mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
bytecode analysis: hash based keys
This commit is contained in:
+261
-246
@@ -15,97 +15,265 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.ApplicationComponent;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import gnu.trove.TLongArrayList;
|
||||
import gnu.trove.TLongHashSet;
|
||||
import gnu.trove.TLongObjectHashMap;
|
||||
import gnu.trove.TLongObjectIterator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.org.objectweb.asm.Type;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public abstract class BytecodeAnalysisConverter implements ApplicationComponent {
|
||||
public class BytecodeAnalysisConverter {
|
||||
|
||||
public static final int SHIFT = 4096;
|
||||
// how many bytes are taken from class fqn digest
|
||||
public static final int CLASS_HASH_SIZE = 10;
|
||||
// how many bytes are taken from signature digest
|
||||
public static final int SIGNATURE_HASH_SIZE = 4;
|
||||
public static final int HASH_SIZE = CLASS_HASH_SIZE + SIGNATURE_HASH_SIZE;
|
||||
|
||||
public static BytecodeAnalysisConverter getInstance() {
|
||||
return ApplicationManager.getApplication().getComponent(BytecodeAnalysisConverter.class);
|
||||
public static MessageDigest getMessageDigest() throws NoSuchAlgorithmException {
|
||||
return MessageDigest.getInstance("MD5");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an equation over asm keys into equation over small hash keys.
|
||||
*/
|
||||
@NotNull
|
||||
@Override
|
||||
public String getComponentName() {
|
||||
return "BytecodeAnalysisConverter";
|
||||
}
|
||||
|
||||
public abstract int getVersion();
|
||||
|
||||
protected abstract int enumerateString(@NotNull String s) throws IOException;
|
||||
|
||||
protected abstract int enumerateCompoundKey(@NotNull int[] key) throws IOException;
|
||||
|
||||
IdEquation convert(Equation<Key, Value> equation) throws IOException {
|
||||
static HEquation convert(@NotNull Equation<Key, Value> equation, @NotNull MessageDigest md) {
|
||||
ProgressManager.checkCanceled();
|
||||
|
||||
Result<Key, Value> rhs = equation.rhs;
|
||||
IdResult result;
|
||||
HResult result;
|
||||
if (rhs instanceof Final) {
|
||||
result = new IdFinal(((Final<Key, Value>)rhs).value);
|
||||
result = new HFinal(((Final<Key, Value>)rhs).value);
|
||||
} else {
|
||||
Pending<Key, Value> pending = (Pending<Key, Value>)rhs;
|
||||
Set<Product<Key, Value>> sumOrigin = pending.sum;
|
||||
IntIdComponent[] components = new IntIdComponent[sumOrigin.size()];
|
||||
HComponent[] components = new HComponent[sumOrigin.size()];
|
||||
int componentI = 0;
|
||||
for (Product<Key, Value> prod : sumOrigin) {
|
||||
long[] intProd = new long[prod.ids.size()];
|
||||
HKey[] intProd = new HKey[prod.ids.size()];
|
||||
int idI = 0;
|
||||
for (Key id : prod.ids) {
|
||||
long rawId = mkAsmKey(id);
|
||||
if (rawId <= 0) {
|
||||
LOG.error("raw key should be positive. rawId = " + rawId);
|
||||
}
|
||||
intProd[idI] = id.stable ? rawId : -rawId;
|
||||
intProd[idI] = asmKey(id, md);
|
||||
idI++;
|
||||
}
|
||||
IntIdComponent intIdComponent = new IntIdComponent(prod.value, intProd);
|
||||
HComponent intIdComponent = new HComponent(prod.value, intProd);
|
||||
components[componentI] = intIdComponent;
|
||||
componentI++;
|
||||
}
|
||||
result = new IdPending(components);
|
||||
result = new HPending(components);
|
||||
}
|
||||
|
||||
long rawKey = mkAsmKey(equation.id);
|
||||
if (rawKey <= 0) {
|
||||
LOG.error("raw key should be positive. rawKey = " + rawKey);
|
||||
HKey key = asmKey(equation.id, md);
|
||||
return new HEquation(key, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an asm method key to a small hash key (HKey)
|
||||
*/
|
||||
@NotNull
|
||||
public static HKey asmKey(@NotNull Key key, @NotNull MessageDigest md) {
|
||||
String signature = key.method.methodName + key.method.methodDesc;
|
||||
byte[] classDigest = md.digest(key.method.internalClassName.getBytes());
|
||||
byte[] sigDigest = md.digest(signature.getBytes());
|
||||
byte[] digest = new byte[HASH_SIZE];
|
||||
System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
|
||||
System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
|
||||
return new HKey(digest, mkDirectionKey(key.direction), key.stable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Psi method to a small hash key (HKey).
|
||||
* Returns null if conversion is impossible (something is not resolvable).
|
||||
*/
|
||||
@Nullable
|
||||
public static HKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) {
|
||||
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
|
||||
if (psiClass == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] classDigest = psiClassDigest(psiClass, md);
|
||||
if (classDigest == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] sigDigest = methodDigest(psiMethod, md);
|
||||
if (sigDigest == null) {
|
||||
return null;
|
||||
}
|
||||
byte[] digest = new byte[HASH_SIZE];
|
||||
System.arraycopy(classDigest, 0, digest, 0, CLASS_HASH_SIZE);
|
||||
System.arraycopy(sigDigest, 0, digest, CLASS_HASH_SIZE, SIGNATURE_HASH_SIZE);
|
||||
return new HKey(digest, mkDirectionKey(direction), true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] psiClassDigest(@NotNull PsiClass psiClass, @NotNull MessageDigest md) {
|
||||
String descriptor = descriptor(psiClass, 0, false);
|
||||
if (descriptor == null) {
|
||||
return null;
|
||||
}
|
||||
return md.digest(descriptor.getBytes());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static byte[] methodDigest(@NotNull PsiMethod psiMethod, @NotNull MessageDigest md) {
|
||||
String descriptor = descriptor(psiMethod);
|
||||
if (descriptor == null) {
|
||||
return null;
|
||||
}
|
||||
return md.digest(descriptor.getBytes());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String descriptor(@NotNull PsiMethod psiMethod) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
|
||||
if (psiClass == null) {
|
||||
return null;
|
||||
}
|
||||
PsiClass outerClass = psiClass.getContainingClass();
|
||||
boolean isInnerClassConstructor = psiMethod.isConstructor() && (outerClass != null) && !psiClass.hasModifierProperty(PsiModifier.STATIC);
|
||||
PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
|
||||
PsiType returnType = psiMethod.getReturnType();
|
||||
|
||||
sb.append(returnType == null ? "<init>" : psiMethod.getName());
|
||||
sb.append('(');
|
||||
|
||||
String desc;
|
||||
|
||||
if (isInnerClassConstructor) {
|
||||
desc = descriptor(outerClass, 0, true);
|
||||
if (desc == null) {
|
||||
return null;
|
||||
}
|
||||
sb.append(desc);
|
||||
}
|
||||
for (PsiParameter parameter : parameters) {
|
||||
desc = descriptor(parameter.getType());
|
||||
if (desc == null) {
|
||||
return null;
|
||||
}
|
||||
sb.append(desc);
|
||||
}
|
||||
sb.append(')');
|
||||
if (returnType == null) {
|
||||
sb.append('V');
|
||||
} else {
|
||||
desc = descriptor(returnType);
|
||||
if (desc == null) {
|
||||
return null;
|
||||
} else {
|
||||
sb.append(desc);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String descriptor(@NotNull PsiClass psiClass, int dimensions, boolean full) {
|
||||
PsiFile containingFile = psiClass.getContainingFile();
|
||||
if (!(containingFile instanceof PsiClassOwner)) {
|
||||
LOG.debug("containingFile was not resolved for " + psiClass.getQualifiedName());
|
||||
return null;
|
||||
}
|
||||
PsiClassOwner psiFile = (PsiClassOwner)containingFile;
|
||||
String packageName = psiFile.getPackageName();
|
||||
String qname = psiClass.getQualifiedName();
|
||||
if (qname == null) {
|
||||
return null;
|
||||
}
|
||||
String className;
|
||||
if (packageName.length() > 0) {
|
||||
className = qname.substring(packageName.length() + 1).replace('.', '$');
|
||||
} else {
|
||||
className = qname.replace('.', '$');
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < dimensions; i++) {
|
||||
sb.append('[');
|
||||
}
|
||||
if (full) {
|
||||
sb.append('L');
|
||||
}
|
||||
if (packageName.length() > 0) {
|
||||
sb.append(packageName.replace('.', '/'));
|
||||
sb.append('/');
|
||||
}
|
||||
sb.append(className);
|
||||
if (full) {
|
||||
sb.append(';');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String descriptor(@NotNull PsiType psiType) {
|
||||
int dimensions = 0;
|
||||
psiType = TypeConversionUtil.erasure(psiType);
|
||||
if (psiType instanceof PsiArrayType) {
|
||||
PsiArrayType arrayType = (PsiArrayType)psiType;
|
||||
psiType = arrayType.getDeepComponentType();
|
||||
dimensions = arrayType.getArrayDimensions();
|
||||
}
|
||||
|
||||
long key = equation.id.stable ? rawKey : -rawKey;
|
||||
return new IdEquation(key, result);
|
||||
if (psiType instanceof PsiClassType) {
|
||||
PsiClass psiClass = ((PsiClassType)psiType).resolve();
|
||||
if (psiClass != null) {
|
||||
return descriptor(psiClass, dimensions, true);
|
||||
}
|
||||
else {
|
||||
LOG.debug("resolve was null for " + ((PsiClassType)psiType).getClassName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (psiType instanceof PsiPrimitiveType) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < dimensions; i++) {
|
||||
sb.append('[');
|
||||
}
|
||||
if (PsiType.VOID.equals(psiType)) {
|
||||
sb.append('V');
|
||||
}
|
||||
else if (PsiType.BOOLEAN.equals(psiType)) {
|
||||
sb.append('Z');
|
||||
}
|
||||
else if (PsiType.CHAR.equals(psiType)) {
|
||||
sb.append('C');
|
||||
}
|
||||
else if (PsiType.BYTE.equals(psiType)) {
|
||||
sb.append('B');
|
||||
}
|
||||
else if (PsiType.SHORT.equals(psiType)) {
|
||||
sb.append('S');
|
||||
}
|
||||
else if (PsiType.INT.equals(psiType)) {
|
||||
sb.append('I');
|
||||
}
|
||||
else if (PsiType.FLOAT.equals(psiType)) {
|
||||
sb.append('F');
|
||||
}
|
||||
else if (PsiType.LONG.equals(psiType)) {
|
||||
sb.append('J');
|
||||
}
|
||||
else if (PsiType.DOUBLE.equals(psiType)) {
|
||||
sb.append('D');
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public long mkAsmKey(@NotNull Key key) throws IOException {
|
||||
long baseKey = mkAsmSignatureKey(key.method);
|
||||
long directionKey = mkDirectionKey(key.direction);
|
||||
return baseKey * SHIFT + directionKey;
|
||||
}
|
||||
|
||||
private static int mkDirectionKey(Direction dir) throws IOException {
|
||||
private static int mkDirectionKey(Direction dir) {
|
||||
if (dir instanceof Out) {
|
||||
return 0;
|
||||
} else if (dir instanceof In) {
|
||||
@@ -134,211 +302,54 @@ public abstract class BytecodeAnalysisConverter implements ApplicationComponent
|
||||
}
|
||||
}
|
||||
|
||||
// class + short signature
|
||||
private int mkAsmSignatureKey(@NotNull Method method) throws IOException {
|
||||
int[] sigKey = new int[2];
|
||||
sigKey[0] = mkAsmTypeKey(Type.getObjectType(method.internalClassName));
|
||||
sigKey[1] = mkAsmShortSignatureKey(method);
|
||||
return enumerateCompoundKey(sigKey);
|
||||
}
|
||||
|
||||
private int mkAsmShortSignatureKey(@NotNull Method method) throws IOException {
|
||||
Type[] argTypes = Type.getArgumentTypes(method.methodDesc);
|
||||
int arity = argTypes.length;
|
||||
int[] sigKey = new int[2 + arity];
|
||||
sigKey[0] = mkAsmTypeKey(Type.getReturnType(method.methodDesc));
|
||||
sigKey[1] = enumerateString(method.methodName);
|
||||
for (int i = 0; i < argTypes.length; i++) {
|
||||
sigKey[2 + i] = mkAsmTypeKey(argTypes[i]);
|
||||
}
|
||||
return enumerateCompoundKey(sigKey);
|
||||
}
|
||||
|
||||
private int mkAsmTypeKey(Type type) throws IOException {
|
||||
String className = type.getClassName();
|
||||
int dotIndex = className.lastIndexOf('.');
|
||||
String packageName;
|
||||
String simpleName;
|
||||
if (dotIndex > 0) {
|
||||
packageName = className.substring(0, dotIndex);
|
||||
simpleName = className.substring(dotIndex + 1);
|
||||
} else {
|
||||
packageName = "";
|
||||
simpleName = className;
|
||||
}
|
||||
int[] classKey = new int[]{enumerateString(packageName), enumerateString(simpleName)};
|
||||
return enumerateCompoundKey(classKey);
|
||||
}
|
||||
|
||||
public long mkPsiKey(@NotNull PsiMethod psiMethod, Direction direction) throws IOException {
|
||||
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
|
||||
if (psiClass == null) {
|
||||
LOG.debug("PsiClass was null for " + psiMethod.getName());
|
||||
return -1;
|
||||
}
|
||||
long sigKey = mkPsiSignatureKey(psiMethod);
|
||||
if (sigKey == -1) {
|
||||
return -1;
|
||||
}
|
||||
long directionKey = mkDirectionKey(direction);
|
||||
return sigKey * SHIFT + directionKey;
|
||||
}
|
||||
|
||||
private int mkPsiSignatureKey(@NotNull PsiMethod psiMethod) throws IOException {
|
||||
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
|
||||
if (psiClass == null) {
|
||||
LOG.debug("PsiClass was null for " + psiMethod.getName());
|
||||
return -1;
|
||||
}
|
||||
PsiClass outerClass = psiClass.getContainingClass();
|
||||
boolean isInnerClassConstructor = psiMethod.isConstructor() && (outerClass != null) && !psiClass.hasModifierProperty(PsiModifier.STATIC);
|
||||
/**
|
||||
* Given a PSI method and its primary HKey enumerate all contract keys for it.
|
||||
*/
|
||||
@NotNull
|
||||
public static ArrayList<HKey> mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull HKey primaryKey) {
|
||||
PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
|
||||
PsiType returnType = psiMethod.getReturnType();
|
||||
|
||||
final int shift = isInnerClassConstructor ? 1 : 0;
|
||||
final int arity = parameters.length + shift;
|
||||
int[] shortSigKey = new int[2 + arity];
|
||||
if (returnType == null) {
|
||||
shortSigKey[0] = mkPsiTypeKey(PsiType.VOID);
|
||||
shortSigKey[1] = enumerateString("<init>");
|
||||
} else {
|
||||
shortSigKey[0] = mkPsiTypeKey(returnType);
|
||||
shortSigKey[1] = enumerateString(psiMethod.getName());
|
||||
}
|
||||
if (isInnerClassConstructor) {
|
||||
shortSigKey[2] = mkPsiClassKey(outerClass, 0);
|
||||
}
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
PsiParameter parameter = parameters[i];
|
||||
shortSigKey[2 + i + shift] = mkPsiTypeKey(parameter.getType());
|
||||
}
|
||||
for (int aShortSigKey : shortSigKey) {
|
||||
if (aShortSigKey == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int[] sigKey = new int[2];
|
||||
int classKey = mkPsiClassKey(psiClass, 0);
|
||||
if (classKey == -1) {
|
||||
return -1;
|
||||
}
|
||||
sigKey[0] = classKey;
|
||||
sigKey[1] = enumerateCompoundKey(shortSigKey);
|
||||
|
||||
return enumerateCompoundKey(sigKey);
|
||||
}
|
||||
|
||||
public TLongArrayList mkInOutKeys(@NotNull PsiMethod psiMethod, long primaryKey) throws IOException {
|
||||
PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
|
||||
TLongArrayList keys = new TLongArrayList(parameters.length * 2 + 1);
|
||||
ArrayList<HKey> keys = new ArrayList<HKey>(parameters.length * 2 + 1);
|
||||
for (int i = 0; i < parameters.length; i++) {
|
||||
PsiParameter parameter = parameters[i];
|
||||
PsiType parameterType = parameter.getType();
|
||||
if (parameterType instanceof PsiPrimitiveType) {
|
||||
if (PsiType.BOOLEAN.equals(parameterType)) {
|
||||
keys.add(primaryKey + mkDirectionKey(new InOut(i, Value.False)));
|
||||
keys.add(primaryKey + mkDirectionKey(new InOut(i, Value.True)));
|
||||
keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.False))));
|
||||
keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.True))));
|
||||
}
|
||||
} else {
|
||||
keys.add(primaryKey + mkDirectionKey(new InOut(i, Value.NotNull)));
|
||||
keys.add(primaryKey + mkDirectionKey(new InOut(i, Value.Null)));
|
||||
keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.NotNull))));
|
||||
keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.Null))));
|
||||
}
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
private int mkPsiClassKey(PsiClass psiClass, int dimensions) throws IOException {
|
||||
PsiFile containingFile = psiClass.getContainingFile();
|
||||
if (!(containingFile instanceof PsiClassOwner)) {
|
||||
LOG.debug("containingFile was not resolved for " + psiClass.getQualifiedName());
|
||||
return -1;
|
||||
}
|
||||
PsiClassOwner psiFile = (PsiClassOwner)containingFile;
|
||||
String packageName = psiFile.getPackageName();
|
||||
String qname = psiClass.getQualifiedName();
|
||||
if (qname == null) {
|
||||
return -1;
|
||||
}
|
||||
String className = qname;
|
||||
if (packageName.length() > 0) {
|
||||
className = qname.substring(packageName.length() + 1).replace('.', '$');
|
||||
}
|
||||
int[] classKey = new int[2];
|
||||
classKey[0] = enumerateString(packageName);
|
||||
if (dimensions == 0) {
|
||||
classKey[1] = enumerateString(className);
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder(className);
|
||||
for (int j = 0; j < dimensions; j++) {
|
||||
sb.append("[]");
|
||||
}
|
||||
classKey[1] = enumerateString(sb.toString());
|
||||
}
|
||||
return enumerateCompoundKey(classKey);
|
||||
}
|
||||
|
||||
private int mkPsiTypeKey(PsiType psiType) throws IOException {
|
||||
int dimensions = 0;
|
||||
psiType = TypeConversionUtil.erasure(psiType);
|
||||
if (psiType instanceof PsiArrayType) {
|
||||
PsiArrayType arrayType = (PsiArrayType)psiType;
|
||||
psiType = arrayType.getDeepComponentType();
|
||||
dimensions = arrayType.getArrayDimensions();
|
||||
}
|
||||
|
||||
if (psiType instanceof PsiClassType) {
|
||||
// no resolve() -> no package/class split
|
||||
PsiClass psiClass = ((PsiClassType)psiType).resolve();
|
||||
if (psiClass != null) {
|
||||
return mkPsiClassKey(psiClass, dimensions);
|
||||
}
|
||||
else {
|
||||
LOG.debug("resolve was null for " + ((PsiClassType)psiType).getClassName());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else if (psiType instanceof PsiPrimitiveType) {
|
||||
String packageName = "";
|
||||
String className = psiType.getPresentableText();
|
||||
int[] classKey = new int[2];
|
||||
classKey[0] = enumerateString(packageName);
|
||||
if (dimensions == 0) {
|
||||
classKey[1] = enumerateString(className);
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder(className);
|
||||
for (int j = 0; j < dimensions; j++) {
|
||||
sb.append("[]");
|
||||
}
|
||||
classKey[1] = enumerateString(sb.toString());
|
||||
}
|
||||
return enumerateCompoundKey(classKey);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void addMethodAnnotations(TLongObjectHashMap<Value> internalIdSolutions, Annotations annotations, long methodKey, int arity) {
|
||||
|
||||
/**
|
||||
* Given `solution` of all dependencies of a method with the `methodKey`, converts this solution into annotations.
|
||||
*
|
||||
* @param solution solution of equations
|
||||
* @param annotations annotations to which corresponding solutions should be added
|
||||
* @param methodKey a primary key of a method being analyzed
|
||||
* @param arity arity of this method (hint for constructing @Contract annotations)
|
||||
*/
|
||||
public static void addMethodAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull Annotations annotations, @NotNull HKey methodKey, int arity) {
|
||||
List<String> clauses = new ArrayList<String>();
|
||||
TLongObjectIterator<Value> solutionsIterator = internalIdSolutions.iterator();
|
||||
|
||||
TLongHashSet notNulls = annotations.notNulls;
|
||||
TLongObjectHashMap<String> contracts = annotations.contracts;
|
||||
for (int i = internalIdSolutions.size(); i-- > 0;) {
|
||||
solutionsIterator.advance();
|
||||
long key = Math.abs(solutionsIterator.key());
|
||||
Value value = solutionsIterator.value();
|
||||
HashSet<HKey> notNulls = annotations.notNulls;
|
||||
HashMap<HKey, String> contracts = annotations.contracts;
|
||||
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
|
||||
HKey key = entry.getKey().mkStable();
|
||||
Value value = entry.getValue();
|
||||
if (value == Value.Top || value == Value.Bot) {
|
||||
continue;
|
||||
}
|
||||
Direction direction = extractDirection((int)(key % SHIFT));
|
||||
if (value == Value.NotNull && direction instanceof Out && key == methodKey) {
|
||||
Direction direction = extractDirection(key.dirKey);
|
||||
if (value == Value.NotNull && direction instanceof Out && methodKey.equals(key)) {
|
||||
notNulls.add(key);
|
||||
}
|
||||
else if (direction instanceof InOut) {
|
||||
long baseKey = key - (key % SHIFT);
|
||||
if (baseKey == methodKey) {
|
||||
HKey baseKey = key.mkBase();
|
||||
if (methodKey.equals(baseKey)) {
|
||||
clauses.add(contractElement(arity, (InOut)direction, value));
|
||||
}
|
||||
}
|
||||
@@ -353,24 +364,28 @@ public abstract class BytecodeAnalysisConverter implements ApplicationComponent
|
||||
}
|
||||
}
|
||||
|
||||
public void addParameterAnnotations(TLongObjectHashMap<Value> internalIdSolutions, Annotations annotations) {
|
||||
TLongObjectIterator<Value> solutionsIterator = internalIdSolutions.iterator();
|
||||
TLongHashSet notNulls = annotations.notNulls;
|
||||
for (int i = internalIdSolutions.size(); i-- > 0;) {
|
||||
solutionsIterator.advance();
|
||||
long key = Math.abs(solutionsIterator.key());
|
||||
Value value = solutionsIterator.value();
|
||||
/**
|
||||
* Converts solutions for equations over parameters into annotations.
|
||||
*
|
||||
* @param solution
|
||||
* @param annotations
|
||||
*/
|
||||
public static void addParameterAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull Annotations annotations) {
|
||||
HashSet<HKey> notNulls = annotations.notNulls;
|
||||
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
|
||||
HKey key = entry.getKey().mkStable();
|
||||
Value value = entry.getValue();
|
||||
if (value == Value.Top || value == Value.Bot) {
|
||||
continue;
|
||||
}
|
||||
Direction direction = extractDirection((int)(key % SHIFT));
|
||||
Direction direction = extractDirection(key.dirKey);
|
||||
if (value == Value.NotNull && (direction instanceof In || direction instanceof Out)) {
|
||||
notNulls.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String contractValueString(Value v) {
|
||||
private static String contractValueString(@NotNull Value v) {
|
||||
switch (v) {
|
||||
case False: return "false";
|
||||
case True: return "true";
|
||||
@@ -380,7 +395,7 @@ public abstract class BytecodeAnalysisConverter implements ApplicationComponent
|
||||
}
|
||||
}
|
||||
|
||||
static String contractElement(int arity, InOut inOut, Value value) {
|
||||
private static String contractElement(int arity, InOut inOut, Value value) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < arity; i++) {
|
||||
Value currentValue = Value.Top;
|
||||
|
||||
-206
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.util.ThrowableComputable;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.util.io.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.DataOutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class BytecodeAnalysisConverterImpl extends BytecodeAnalysisConverter {
|
||||
private static final int LOGIC_VERSION = 1;
|
||||
private static final String ENUMERATORS_VERSION_KEY = "BytecodeAnalysisConverter.Enumerators";
|
||||
|
||||
private File myVersionFile;
|
||||
private PersistentStringEnumerator myNamesEnumerator;
|
||||
private PersistentEnumeratorDelegate<int[]> myCompoundKeyEnumerator;
|
||||
private int version;
|
||||
|
||||
@Override
|
||||
public void initComponent() {
|
||||
|
||||
// suffix as an indicator of version
|
||||
final File keysDir = new File(PathManager.getIndexRoot(), "bytecodekeys");
|
||||
final File namesFile = new File(keysDir, "names" + LOGIC_VERSION);
|
||||
final File compoundKeysFile = new File(keysDir, "compound" + LOGIC_VERSION);
|
||||
myVersionFile = new File(keysDir, "version" + LOGIC_VERSION);
|
||||
|
||||
version = PropertiesComponent.getInstance().getOrInitInt(ENUMERATORS_VERSION_KEY, 0);
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
version = _readVersion();
|
||||
}
|
||||
|
||||
if (!namesFile.exists() || !compoundKeysFile.exists() || !myVersionFile.exists()) {
|
||||
LOG.info("No enumerators detected, re-initialization of enumerators.");
|
||||
IOUtil.deleteAllFilesStartingWith(keysDir);
|
||||
version++;
|
||||
}
|
||||
|
||||
try {
|
||||
IOUtil.openCleanOrResetBroken(new ThrowableComputable<Void, IOException>() {
|
||||
@Override
|
||||
public Void compute() throws IOException {
|
||||
myNamesEnumerator = new PersistentStringEnumerator(namesFile, true);
|
||||
myCompoundKeyEnumerator = new IntArrayPersistentEnumerator(compoundKeysFile, new IntArrayKeyDescriptor());
|
||||
return null;
|
||||
}
|
||||
}, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Error during initialization of enumerators in bytecode analysis. Re-initializing.");
|
||||
IOUtil.deleteAllFilesStartingWith(keysDir);
|
||||
version++;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("Re-initialization of enumerators in bytecode analysis failed.", e);
|
||||
}
|
||||
PropertiesComponent.getInstance().setValue(ENUMERATORS_VERSION_KEY, String.valueOf(version));
|
||||
_saveVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disposeComponent() {
|
||||
try {
|
||||
myNamesEnumerator.close();
|
||||
myCompoundKeyEnumerator.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.debug(e);
|
||||
}
|
||||
}
|
||||
|
||||
public int _readVersion() {
|
||||
try {
|
||||
final DataInputStream is = new DataInputStream(new FileInputStream(myVersionFile));
|
||||
try {
|
||||
return is.readInt();
|
||||
}
|
||||
finally {
|
||||
is.close();
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ignored) {
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void _saveVersion() {
|
||||
try {
|
||||
FileUtil.createIfDoesntExist(myVersionFile);
|
||||
final DataOutputStream os = new DataOutputStream(new FileOutputStream(myVersionFile));
|
||||
try {
|
||||
os.writeInt(version);
|
||||
}
|
||||
finally {
|
||||
os.close();
|
||||
}
|
||||
}
|
||||
catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int enumerateString(@NotNull String s) throws IOException {
|
||||
return myNamesEnumerator.enumerate(s);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int enumerateCompoundKey(@NotNull int[] key) throws IOException {
|
||||
return myCompoundKeyEnumerator.enumerate(key);
|
||||
}
|
||||
|
||||
private static class IntArrayKeyDescriptor implements KeyDescriptor<int[]>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, int[] value) throws IOException {
|
||||
DataInputOutputUtil.writeINT(out, value.length);
|
||||
for (int i : value) {
|
||||
DataInputOutputUtil.writeINT(out, i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] read(@NotNull DataInput in) throws IOException {
|
||||
int[] value = new int[DataInputOutputUtil.readINT(in)];
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
value[i] = DataInputOutputUtil.readINT(in);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(int[] value) {
|
||||
return Arrays.hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(int[] val1, int[] val2) {
|
||||
return Arrays.equals(val1, val2);
|
||||
}
|
||||
}
|
||||
|
||||
private static class IntArrayPersistentEnumerator extends PersistentEnumeratorDelegate<int[]> {
|
||||
private final CachingEnumerator<int[]> myCache;
|
||||
|
||||
public IntArrayPersistentEnumerator(File compoundKeysFile, IntArrayKeyDescriptor descriptor) throws IOException {
|
||||
super(compoundKeysFile, descriptor, 1024 * 4);
|
||||
myCache = new CachingEnumerator<int[]>(new DataEnumerator<int[]>() {
|
||||
@Override
|
||||
public int enumerate(@Nullable int[] value) throws IOException {
|
||||
return IntArrayPersistentEnumerator.super.enumerate(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public int[] valueOf(int idx) throws IOException {
|
||||
return IntArrayPersistentEnumerator.super.valueOf(idx);
|
||||
}
|
||||
}, descriptor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int enumerate(@Nullable int[] value) throws IOException {
|
||||
return myCache.enumerate(value);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public int[] valueOf(int idx) throws IOException {
|
||||
return myCache.valueOf(idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
-82
@@ -34,12 +34,11 @@ import java.io.IOException;
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Long, IdEquation> {
|
||||
public static final ID<Long, IdEquation> NAME = ID.create("bytecodeAnalysis");
|
||||
private final EquationExternalizer myExternalizer = new EquationExternalizer();
|
||||
private static final DataIndexer<Long, IdEquation, FileContent> INDEXER =
|
||||
new ClassDataIndexer(BytecodeAnalysisConverter.getInstance());
|
||||
private static final SmartLongKeyDescriptor KEY_DESCRIPTOR = new SmartLongKeyDescriptor();
|
||||
public class BytecodeAnalysisIndex extends FileBasedIndexExtension<HKey, HResult> {
|
||||
public static final ID<HKey, HResult> NAME = ID.create("bytecodeAnalysis");
|
||||
private final HEquationExternalizer myExternalizer = new HEquationExternalizer();
|
||||
private static final ClassDataIndexer INDEXER = new ClassDataIndexer();
|
||||
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
|
||||
|
||||
private static final int ourInternalVersion = 3;
|
||||
private static boolean ourEnabled = SystemProperties.getBooleanProperty("idea.enable.bytecode.contract.inference", isEnabledByDefault());
|
||||
@@ -51,25 +50,25 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Long, IdEquat
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ID<Long, IdEquation> getName() {
|
||||
public ID<HKey, HResult> getName() {
|
||||
return NAME;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataIndexer<Long, IdEquation, FileContent> getIndexer() {
|
||||
public DataIndexer<HKey, HResult, FileContent> getIndexer() {
|
||||
return INDEXER;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public KeyDescriptor<Long> getKeyDescriptor() {
|
||||
public KeyDescriptor<HKey> getKeyDescriptor() {
|
||||
return KEY_DESCRIPTOR;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public DataExternalizer<IdEquation> getValueExternalizer() {
|
||||
public DataExternalizer<HResult> getValueExternalizer() {
|
||||
return myExternalizer;
|
||||
}
|
||||
|
||||
@@ -91,111 +90,85 @@ public class BytecodeAnalysisIndex extends FileBasedIndexExtension<Long, IdEquat
|
||||
|
||||
@Override
|
||||
public int getVersion() {
|
||||
return ourInternalVersion + BytecodeAnalysisConverter.getInstance().getVersion() + (ourEnabled ? 0xFF : 0);
|
||||
return ourInternalVersion + (ourEnabled ? 0xFF : 0);
|
||||
}
|
||||
|
||||
public static class EquationExternalizer implements DataExternalizer<IdEquation>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
private static class HKeyDescriptor implements KeyDescriptor<HKey>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, IdEquation equation) throws IOException {
|
||||
long id = equation.id;
|
||||
int sign = id > 0 ? 1 : -1;
|
||||
id = Math.abs(id);
|
||||
int primaryId = (int)(id / BytecodeAnalysisConverter.SHIFT);
|
||||
int secondaryId = (int)(id % BytecodeAnalysisConverter.SHIFT);
|
||||
out.writeInt(sign * primaryId);
|
||||
DataInputOutputUtil.writeINT(out, secondaryId);
|
||||
IdResult rhs = equation.rhs;
|
||||
if (rhs instanceof IdFinal) {
|
||||
IdFinal finalResult = (IdFinal)rhs;
|
||||
public void save(@NotNull DataOutput out, HKey value) throws IOException {
|
||||
out.write(value.key);
|
||||
DataInputOutputUtil.writeINT(out, value.dirKey);
|
||||
out.writeBoolean(value.stable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HKey read(@NotNull DataInput in) throws IOException {
|
||||
byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = in.readByte();
|
||||
}
|
||||
return new HKey(bytes, DataInputOutputUtil.readINT(in), in.readBoolean());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(HKey value) {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(HKey val1, HKey val2) {
|
||||
return val1.equals(val2);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HEquationExternalizer implements DataExternalizer<HResult>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, HResult rhs) throws IOException {
|
||||
if (rhs instanceof HFinal) {
|
||||
HFinal finalResult = (HFinal)rhs;
|
||||
out.writeBoolean(true); // final flag
|
||||
DataInputOutputUtil.writeINT(out, finalResult.value.ordinal());
|
||||
} else {
|
||||
IdPending pendResult = (IdPending)rhs;
|
||||
HPending pendResult = (HPending)rhs;
|
||||
out.writeBoolean(false); // pending flag
|
||||
DataInputOutputUtil.writeINT(out, pendResult.delta.length);
|
||||
|
||||
for (IntIdComponent component : pendResult.delta) {
|
||||
for (HComponent component : pendResult.delta) {
|
||||
DataInputOutputUtil.writeINT(out, component.value.ordinal());
|
||||
long[] ids = component.ids;
|
||||
HKey[] ids = component.ids;
|
||||
DataInputOutputUtil.writeINT(out, ids.length);
|
||||
for (long id1 : ids) {
|
||||
sign = id1 > 0 ? 1 : -1;
|
||||
id = Math.abs(id1);
|
||||
primaryId = (int)(id / BytecodeAnalysisConverter.SHIFT);
|
||||
secondaryId = (int)(id % BytecodeAnalysisConverter.SHIFT);
|
||||
out.writeInt(sign * primaryId);
|
||||
DataInputOutputUtil.writeINT(out, secondaryId);
|
||||
for (HKey id1 : ids) {
|
||||
KEY_DESCRIPTOR.save(out, id1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdEquation read(@NotNull DataInput in) throws IOException {
|
||||
long primaryId = in.readInt();
|
||||
int sign = primaryId > 0 ? 1 : -1;
|
||||
primaryId = Math.abs(primaryId);
|
||||
int secondaryId = DataInputOutputUtil.readINT(in);
|
||||
long equationId = sign * (primaryId * BytecodeAnalysisConverter.SHIFT + secondaryId);
|
||||
public HResult read(@NotNull DataInput in) throws IOException {
|
||||
boolean isFinal = in.readBoolean(); // flag
|
||||
if (isFinal) {
|
||||
int ordinal = DataInputOutputUtil.readINT(in);
|
||||
Value value = Value.values()[ordinal];
|
||||
return new IdEquation(equationId, new IdFinal(value));
|
||||
return new HFinal(value);
|
||||
} else {
|
||||
|
||||
int sumLength = DataInputOutputUtil.readINT(in);
|
||||
IntIdComponent[] components = new IntIdComponent[sumLength];
|
||||
HComponent[] components = new HComponent[sumLength];
|
||||
|
||||
for (int i = 0; i < sumLength; i++) {
|
||||
int ordinal = DataInputOutputUtil.readINT(in);
|
||||
Value value = Value.values()[ordinal];
|
||||
int componentSize = DataInputOutputUtil.readINT(in);
|
||||
long[] ids = new long[componentSize];
|
||||
HKey[] ids = new HKey[componentSize];
|
||||
for (int j = 0; j < componentSize; j++) {
|
||||
primaryId = in.readInt();
|
||||
sign = primaryId > 0 ? 1 : -1;
|
||||
primaryId = Math.abs(primaryId);
|
||||
secondaryId = DataInputOutputUtil.readINT(in);
|
||||
long id = sign * (primaryId * BytecodeAnalysisConverter.SHIFT + secondaryId);
|
||||
ids[j] = id;
|
||||
ids[j] = KEY_DESCRIPTOR.read(in);
|
||||
}
|
||||
components[i] = new IntIdComponent(value, ids);
|
||||
components[i] = new HComponent(value, ids);
|
||||
}
|
||||
return new IdEquation(equationId, new IdPending(components));
|
||||
return new HPending(components);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SmartLongKeyDescriptor implements KeyDescriptor<Long>, DifferentSerializableBytesImplyNonEqualityPolicy {
|
||||
@Override
|
||||
public void save(@NotNull DataOutput out, Long value) throws IOException {
|
||||
long id = value.longValue();
|
||||
int sign = id > 0 ? 1 : -1;
|
||||
id = Math.abs(id);
|
||||
int primaryId = (int)(id / BytecodeAnalysisConverter.SHIFT);
|
||||
int secondaryId = (int)(id % BytecodeAnalysisConverter.SHIFT);
|
||||
out.writeInt(primaryId * sign);
|
||||
DataInputOutputUtil.writeINT(out, secondaryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long read(@NotNull DataInput in) throws IOException {
|
||||
long primaryId = in.readInt();
|
||||
int sign = primaryId > 0 ? 1 : -1;
|
||||
primaryId = Math.abs(primaryId);
|
||||
int secondaryId = DataInputOutputUtil.readINT(in);
|
||||
return sign * (primaryId * BytecodeAnalysisConverter.SHIFT + secondaryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHashCode(Long value) {
|
||||
return value.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqual(Long val1, Long val2) {
|
||||
return val1.longValue() == val2.longValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
-28
@@ -27,6 +27,7 @@ import org.jetbrains.org.objectweb.asm.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -37,29 +38,25 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
|
||||
/**
|
||||
* @author lambdamix
|
||||
*/
|
||||
public class ClassDataIndexer implements DataIndexer<Long, IdEquation, FileContent> {
|
||||
final BytecodeAnalysisConverter myConverter;
|
||||
|
||||
public ClassDataIndexer(BytecodeAnalysisConverter converter) {
|
||||
myConverter = converter;
|
||||
}
|
||||
public class ClassDataIndexer implements DataIndexer<HKey, HResult, FileContent> {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Map<Long, IdEquation> map(@NotNull FileContent inputData) {
|
||||
HashMap<Long, IdEquation> map = new HashMap<Long, IdEquation>();
|
||||
public Map<HKey, HResult> map(@NotNull FileContent inputData) {
|
||||
HashMap<HKey, HResult> map = new HashMap<HKey, HResult>();
|
||||
try {
|
||||
ClassEquations rawEquations = processClass(new ClassReader(inputData.getContent()));
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
ClassEquations rawEquations = processClass(new ClassReader(inputData.getContent()), inputData.getFile().getPresentableUrl());
|
||||
List<Equation<Key, Value>> rawParameterEquations = rawEquations.parameterEquations;
|
||||
List<Equation<Key, Value>> rawContractEquations = rawEquations.contractEquations;
|
||||
|
||||
for (Equation<Key, Value> rawParameterEquation: rawParameterEquations) {
|
||||
IdEquation equation = myConverter.convert(rawParameterEquation);
|
||||
map.put(equation.id, equation);
|
||||
HEquation equation = BytecodeAnalysisConverter.convert(rawParameterEquation, md);
|
||||
map.put(equation.key, equation.result);
|
||||
}
|
||||
for (Equation<Key, Value> rawContractEquation: rawContractEquations) {
|
||||
IdEquation equation = myConverter.convert(rawContractEquation);
|
||||
map.put(equation.id, equation);
|
||||
HEquation equation = BytecodeAnalysisConverter.convert(rawContractEquation, md);
|
||||
map.put(equation.key, equation.result);
|
||||
}
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
@@ -83,7 +80,7 @@ public class ClassDataIndexer implements DataIndexer<Long, IdEquation, FileConte
|
||||
}
|
||||
}
|
||||
|
||||
public static ClassEquations processClass(final ClassReader classReader) {
|
||||
public static ClassEquations processClass(final ClassReader classReader, final String presentableUrl) {
|
||||
final List<Equation<Key, Value>> parameterEquations = new ArrayList<Equation<Key, Value>>();
|
||||
final List<Equation<Key, Value>> contractEquations = new ArrayList<Equation<Key, Value>>();
|
||||
|
||||
@@ -180,6 +177,10 @@ public class ClassDataIndexer implements DataIndexer<Long, IdEquation, FileConte
|
||||
|
||||
boolean[] leakingParameters = maybeLeakingParameter ? cfg.leakingParameters(className, methodNode) : null;
|
||||
|
||||
if (isReferenceResult) {
|
||||
contractEquations.add(resultEquation.getValue());
|
||||
}
|
||||
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
Type argType = argumentTypes[i];
|
||||
int argSort = argType.getSort();
|
||||
@@ -188,32 +189,51 @@ public class ClassDataIndexer implements DataIndexer<Long, IdEquation, FileConte
|
||||
if (isReferenceArg) {
|
||||
if (leakingParameters[i]) {
|
||||
parameterEquations.add(new NonNullInAnalysis(new RichControlFlow(graph, dfs), new In(i), stable).analyze());
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so it is definitely NOT @NotNull
|
||||
parameterEquations.add(new Equation<Key, Value>(new Key(method, new In(i), stable), new Final<Key, Value>(Value.Top)));
|
||||
}
|
||||
}
|
||||
if (isReferenceArg && isInterestingResult) {
|
||||
if (leakingParameters[i]) {
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.Null), resultOrigins.getValue(), stable).analyze());
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.NotNull), resultOrigins.getValue(), stable).analyze());
|
||||
} else {
|
||||
if (resultOrigins.getValue() != null) {
|
||||
// result origins analysis was ok
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.Null), resultOrigins.getValue(), stable).analyze());
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.NotNull), resultOrigins.getValue(), stable).analyze());
|
||||
}
|
||||
else {
|
||||
// result origins analysis failed, approximating to Top
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), new Final<Key, Value>(Value.Top)));
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), new Final<Key, Value>(Value.Top)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so a contract is the same as for the whole method
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), resultEquation.getValue().rhs));
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.NotNull), stable), resultEquation.getValue().rhs));
|
||||
}
|
||||
}
|
||||
if (isBooleanArg && isInterestingResult) {
|
||||
if (leakingParameters[i]) {
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.False), resultOrigins.getValue(), stable).analyze());
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.True), resultOrigins.getValue(), stable).analyze());
|
||||
} else {
|
||||
if (resultOrigins.getValue() != null) {
|
||||
// result origins analysis was ok
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.False), resultOrigins.getValue(), stable).analyze());
|
||||
contractEquations.add(new InOutAnalysis(new RichControlFlow(graph, dfs), new InOut(i, Value.True), resultOrigins.getValue(), stable).analyze());
|
||||
}
|
||||
else {
|
||||
// result origins analysis failed, approximating to Top
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), new Final<Key, Value>(Value.Top)));
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), new Final<Key, Value>(Value.Top)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// parameter is not leaking, so a contract is the same as for the whole method
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.False), stable), resultEquation.getValue().rhs));
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), resultEquation.getValue().rhs));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isReferenceResult) {
|
||||
contractEquations.add(resultEquation.getValue());
|
||||
}
|
||||
added = true;
|
||||
}
|
||||
else {
|
||||
@@ -222,6 +242,9 @@ public class ClassDataIndexer implements DataIndexer<Long, IdEquation, FileConte
|
||||
}
|
||||
|
||||
if (!added) {
|
||||
if (isReferenceResult) {
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new Out(), stable), new Final<Key, Value>(Value.Top)));
|
||||
}
|
||||
for (int i = 0; i < argumentTypes.length; i++) {
|
||||
Type argType = argumentTypes[i];
|
||||
int argSort = argType.getSort();
|
||||
@@ -240,9 +263,6 @@ public class ClassDataIndexer implements DataIndexer<Long, IdEquation, FileConte
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.True), stable), new Final<Key, Value>(Value.Top)));
|
||||
}
|
||||
}
|
||||
if (isReferenceResult) {
|
||||
contractEquations.add(new Equation<Key, Value>(new Key(method, new Out(), stable), new Final<Key, Value>(Value.Top)));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ProcessCanceledException e) {
|
||||
@@ -251,7 +271,7 @@ public class ClassDataIndexer implements DataIndexer<Long, IdEquation, FileConte
|
||||
catch (Throwable e) {
|
||||
// incorrect bytecode may result in Runtime exceptions during analysis
|
||||
// so here we suppose that exception is due to incorrect bytecode
|
||||
LOG.debug("Unexpected Error during processing of " + method, e);
|
||||
LOG.debug("Unexpected Error during processing of " + method + " in " + presentableUrl, e);
|
||||
}
|
||||
}
|
||||
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
|
||||
|
||||
@@ -224,4 +224,5 @@ final class Key {
|
||||
}
|
||||
|
||||
class LimitReachedException extends RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Small size key, constructed by hashing method signature.
|
||||
* @see com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter for details of construction.
|
||||
*/
|
||||
final class HKey {
|
||||
@NotNull
|
||||
final byte[] key;
|
||||
final int dirKey;
|
||||
final boolean stable;
|
||||
|
||||
HKey(@NotNull byte[] key, int dirKey, boolean stable) {
|
||||
this.key = key;
|
||||
this.dirKey = dirKey;
|
||||
this.stable = stable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HKey hKey = (HKey)o;
|
||||
if (dirKey != hKey.dirKey) return false;
|
||||
if (stable != hKey.stable) return false;
|
||||
if (!Arrays.equals(key, hKey.key)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(key);
|
||||
result = 31 * result + dirKey;
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
HKey negate() {
|
||||
return new HKey(key, dirKey, !stable);
|
||||
}
|
||||
|
||||
HKey mkStable() {
|
||||
return stable ? this : new HKey(key, dirKey, true);
|
||||
}
|
||||
|
||||
public HKey mkBase() {
|
||||
return dirKey == 0 ? this : new HKey(key, 0, stable);
|
||||
}
|
||||
|
||||
HKey updateDirection(int newDirKey) {
|
||||
return new HKey(key, newDirKey, stable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "HKey{" +
|
||||
"key=" + Arrays.toString(key) +
|
||||
", dirKey=" + dirKey +
|
||||
", stable=" + stable +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
final class HComponent {
|
||||
@NotNull Value value;
|
||||
@NotNull final HKey[] ids;
|
||||
|
||||
HComponent(@NotNull Value value, @NotNull HKey[] ids) {
|
||||
this.value = value;
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
HComponent that = (HComponent)o;
|
||||
|
||||
if (!Arrays.equals(ids, that.ids)) return false;
|
||||
if (value != that.value) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = value.hashCode();
|
||||
result = 31 * result + Arrays.hashCode(ids);
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean remove(@NotNull HKey id) {
|
||||
return HUtils.remove(ids, id);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return HUtils.isEmpty(ids);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public HComponent copy() {
|
||||
return new HComponent(value, ids.clone());
|
||||
}
|
||||
}
|
||||
|
||||
class HUtils {
|
||||
|
||||
static boolean isEmpty(HKey[] ids) {
|
||||
for (HKey id : ids) {
|
||||
if (id != null) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static boolean remove(HKey[] ids, @NotNull HKey id) {
|
||||
boolean removed = false;
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
if (id.equals(ids[i])) {
|
||||
ids[i] = null;
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
final class HEquation {
|
||||
@NotNull final HKey key;
|
||||
@NotNull final HResult result;
|
||||
|
||||
HEquation(@NotNull HKey key, @NotNull HResult result) {
|
||||
this.key = key;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HEquation hEquation = (HEquation)o;
|
||||
if (!key.equals(hEquation.key)) return false;
|
||||
if (!result.equals(hEquation.result)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result1 = key.hashCode();
|
||||
result1 = 31 * result1 + result.hashCode();
|
||||
return result1;
|
||||
}
|
||||
}
|
||||
|
||||
interface HResult {}
|
||||
|
||||
final class HFinal implements HResult {
|
||||
@NotNull final Value value;
|
||||
|
||||
HFinal(@NotNull Value value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
HFinal hFinal = (HFinal)o;
|
||||
|
||||
if (value != hFinal.value) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.ordinal();
|
||||
}
|
||||
}
|
||||
|
||||
final class HPending implements HResult {
|
||||
@NotNull final HComponent[] delta;
|
||||
|
||||
HPending(@NotNull HComponent[] delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
HPending hPending = (HPending)o;
|
||||
if (!Arrays.equals(delta, hPending.delta)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(delta);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
HPending copy() {
|
||||
HComponent[] delta1 = new HComponent[delta.length];
|
||||
for (int i = 0; i < delta.length; i++) {
|
||||
delta1[i] = delta[i].copy();
|
||||
|
||||
}
|
||||
return new HPending(delta1);
|
||||
}
|
||||
}
|
||||
+47
-45
@@ -30,15 +30,16 @@ import com.intellij.psi.util.CachedValueProvider;
|
||||
import com.intellij.psi.util.CachedValuesManager;
|
||||
import com.intellij.psi.util.PsiFormatUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.LongStack;
|
||||
import com.intellij.util.containers.Stack;
|
||||
import com.intellij.util.indexing.FileBasedIndex;
|
||||
import gnu.trove.TLongArrayList;
|
||||
import gnu.trove.TLongHashSet;
|
||||
import gnu.trove.TLongObjectHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -94,11 +95,12 @@ public class ProjectBytecodeAnalysis {
|
||||
@NotNull
|
||||
private PsiAnnotation[] collectInferredAnnotations(PsiModifierListOwner listOwner) {
|
||||
try {
|
||||
long ownerKey = getKey(listOwner);
|
||||
if (ownerKey == -1) {
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
HKey ownerKey = getKey(listOwner, md);
|
||||
if (ownerKey == null) {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
TLongArrayList allKeys = contractKeys(listOwner, ownerKey);
|
||||
ArrayList<HKey> allKeys = contractKeys(listOwner, ownerKey);
|
||||
Annotations annotations = loadAnnotations(listOwner, ownerKey, allKeys);
|
||||
boolean notNull = annotations.notNulls.contains(ownerKey);
|
||||
String contractValue = annotations.contracts.get(ownerKey);
|
||||
@@ -123,15 +125,15 @@ public class ProjectBytecodeAnalysis {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.debug(e);
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
catch (EquationsLimitException e) {
|
||||
String externalName = PsiFormatUtil.getExternalName(listOwner, false, Integer.MAX_VALUE);
|
||||
LOG.info("Too many equations for " + externalName);
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
LOG.error(e);
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
private PsiAnnotation getNotNullAnnotation() {
|
||||
@@ -148,10 +150,11 @@ public class ProjectBytecodeAnalysis {
|
||||
return createAnnotationFromText("@org.jetbrains.annotations.Contract(" + contractValue + ")");
|
||||
}
|
||||
|
||||
public static long getKey(@NotNull PsiModifierListOwner owner) throws IOException {
|
||||
@Nullable
|
||||
public static HKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) {
|
||||
LOG.assertTrue(owner instanceof PsiCompiledElement, owner);
|
||||
if (owner instanceof PsiMethod) {
|
||||
return BytecodeAnalysisConverter.getInstance().mkPsiKey((PsiMethod)owner, new Out());
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, new Out(), md);
|
||||
}
|
||||
if (owner instanceof PsiParameter) {
|
||||
PsiElement parent = owner.getParent();
|
||||
@@ -159,55 +162,54 @@ public class ProjectBytecodeAnalysis {
|
||||
PsiElement gParent = parent.getParent();
|
||||
if (gParent instanceof PsiMethod) {
|
||||
final int index = ((PsiParameterList)parent).getParameterIndex((PsiParameter)owner);
|
||||
return BytecodeAnalysisConverter.getInstance().mkPsiKey((PsiMethod)gParent, new In(index));
|
||||
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index), md);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static TLongArrayList contractKeys(@NotNull PsiModifierListOwner owner, long primaryKey) throws IOException {
|
||||
public static ArrayList<HKey> contractKeys(@NotNull PsiModifierListOwner owner, HKey primaryKey) {
|
||||
if (owner instanceof PsiMethod) {
|
||||
TLongArrayList result = BytecodeAnalysisConverter.getInstance().mkInOutKeys((PsiMethod)owner, primaryKey);
|
||||
ArrayList<HKey> result = BytecodeAnalysisConverter.mkInOutKeys((PsiMethod)owner, primaryKey);
|
||||
result.add(primaryKey);
|
||||
return result;
|
||||
}
|
||||
TLongArrayList result = new TLongArrayList(1);
|
||||
ArrayList<HKey> result = new ArrayList<HKey>(1);
|
||||
result.add(primaryKey);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Annotations loadAnnotations(@NotNull PsiModifierListOwner owner, long key, TLongArrayList allKeys)
|
||||
throws IOException, EquationsLimitException {
|
||||
private Annotations loadAnnotations(@NotNull PsiModifierListOwner owner, @NotNull HKey key, ArrayList<HKey> allKeys)
|
||||
throws EquationsLimitException {
|
||||
Annotations result = new Annotations();
|
||||
if (owner instanceof PsiParameter) {
|
||||
final Solver solver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top));
|
||||
collectEquations(allKeys, solver);
|
||||
TLongObjectHashMap<Value> solutions = solver.solve();
|
||||
BytecodeAnalysisConverter.getInstance().addParameterAnnotations(solutions, result);
|
||||
HashMap<HKey, Value> solutions = solver.solve();
|
||||
BytecodeAnalysisConverter.addParameterAnnotations(solutions, result);
|
||||
} else if (owner instanceof PsiMethod) {
|
||||
final Solver solver = new Solver(new ELattice<Value>(Value.Bot, Value.Top));
|
||||
collectEquations(allKeys, solver);
|
||||
TLongObjectHashMap<Value> solutions = solver.solve();
|
||||
BytecodeAnalysisConverter.getInstance().addMethodAnnotations(solutions, result, key,
|
||||
((PsiMethod)owner).getParameterList().getParameters().length);
|
||||
HashMap<HKey, Value> solutions = solver.solve();
|
||||
int arity = ((PsiMethod)owner).getParameterList().getParameters().length;
|
||||
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void collectEquations(TLongArrayList keys, Solver solver) throws EquationsLimitException {
|
||||
private void collectEquations(ArrayList<HKey> keys, Solver solver) throws EquationsLimitException {
|
||||
GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject);
|
||||
TLongHashSet queued = new TLongHashSet();
|
||||
LongStack queue = new LongStack();
|
||||
HashSet<HKey> queued = new HashSet<HKey>();
|
||||
Stack<HKey> queue = new Stack<HKey>();
|
||||
|
||||
for (int i = 0; i < keys.size(); i++) {
|
||||
long key = keys.get(i);
|
||||
for (HKey key : keys) {
|
||||
queue.push(key);
|
||||
queued.add(key);
|
||||
// stable/unstable
|
||||
queue.push(-key);
|
||||
queued.add(-key);
|
||||
HKey nKey = key.negate();
|
||||
queue.push(nKey);
|
||||
queued.add(nKey);
|
||||
}
|
||||
|
||||
FileBasedIndex index = FileBasedIndex.getInstance();
|
||||
@@ -216,20 +218,20 @@ public class ProjectBytecodeAnalysis {
|
||||
throw new EquationsLimitException();
|
||||
}
|
||||
ProgressManager.checkCanceled();
|
||||
List<IdEquation> equations = index.getValues(BytecodeAnalysisIndex.NAME, queue.pop(), librariesScope);
|
||||
for (IdEquation equation : equations) {
|
||||
IdResult rhs = equation.rhs;
|
||||
solver.addEquation(equation);
|
||||
if (rhs instanceof IdPending) {
|
||||
IdPending intIdPending = (IdPending)rhs;
|
||||
for (IntIdComponent component : intIdPending.delta) {
|
||||
for (long depKey : component.ids) {
|
||||
HKey hKey = queue.pop();
|
||||
List<HResult> results = index.getValues(BytecodeAnalysisIndex.NAME, hKey, librariesScope);
|
||||
for (HResult result : results) {
|
||||
solver.addEquation(new HEquation(hKey, result));
|
||||
if (result instanceof HPending) {
|
||||
HPending pending = (HPending)result;
|
||||
for (HComponent component : pending.delta) {
|
||||
for (HKey depKey : component.ids) {
|
||||
if (!queued.contains(depKey)) {
|
||||
queue.push(depKey);
|
||||
queued.add(depKey);
|
||||
}
|
||||
// stable/unstable
|
||||
long swapped = -depKey;
|
||||
HKey swapped = depKey.negate();
|
||||
if (!queued.contains(swapped)) {
|
||||
queue.push(swapped);
|
||||
queued.add(swapped);
|
||||
@@ -251,9 +253,9 @@ public class ProjectBytecodeAnalysis {
|
||||
|
||||
class Annotations {
|
||||
// @NotNull keys
|
||||
final TLongHashSet notNulls = new TLongHashSet();
|
||||
final HashSet<HKey> notNulls = new HashSet<HKey>();
|
||||
// @Contracts
|
||||
final TLongObjectHashMap<String> contracts = new TLongObjectHashMap<String>();
|
||||
final HashMap<HKey, String> contracts = new HashMap<HKey, String>();
|
||||
}
|
||||
|
||||
class EquationsLimitException extends Exception {}
|
||||
|
||||
+45
-217
@@ -15,10 +15,6 @@
|
||||
*/
|
||||
package com.intellij.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.util.containers.LongStack;
|
||||
import gnu.trove.TLongHashSet;
|
||||
import gnu.trove.TLongIterator;
|
||||
import gnu.trove.TLongObjectHashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
|
||||
@@ -48,88 +44,6 @@ final class ELattice<T extends Enum<T>> {
|
||||
}
|
||||
}
|
||||
|
||||
// component specialized for ints
|
||||
final class IntIdComponent {
|
||||
Value value;
|
||||
final long[] ids;
|
||||
|
||||
IntIdComponent(Value value, long[] ids) {
|
||||
this.value = value;
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
IntIdComponent that = (IntIdComponent)o;
|
||||
|
||||
if (!Arrays.equals(ids, that.ids)) return false;
|
||||
if (value != that.value) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.ordinal() + Arrays.hashCode(ids);
|
||||
}
|
||||
|
||||
public boolean remove(long id) {
|
||||
return IdUtils.remove(ids, id);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return IdUtils.isEmpty(ids);
|
||||
}
|
||||
|
||||
IntIdComponent copy() {
|
||||
return new IntIdComponent(value, ids.clone());
|
||||
}
|
||||
}
|
||||
|
||||
class IdUtils {
|
||||
// removed value
|
||||
static final long nullId = 0;
|
||||
|
||||
static boolean contains(long[] ids, int id) {
|
||||
for (long id1 : ids) {
|
||||
if (id1 == id) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean isEmpty(long[] ids) {
|
||||
for (long id : ids) {
|
||||
if (id != nullId) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static IntIdComponent[] toArray(Collection<IntIdComponent> set) {
|
||||
IntIdComponent[] result = new IntIdComponent[set.size()];
|
||||
int i = 0;
|
||||
for (IntIdComponent intIdComponent : set) {
|
||||
result[i] = intIdComponent;
|
||||
i++;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static boolean remove(long[] ids, long id) {
|
||||
boolean removed = false;
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
if (ids[i] == id) {
|
||||
ids[i] = nullId;
|
||||
removed = true;
|
||||
}
|
||||
}
|
||||
return removed;
|
||||
}
|
||||
}
|
||||
|
||||
class ResultUtil<Id, T extends Enum<T>> {
|
||||
private final ELattice<T> lattice;
|
||||
@@ -235,94 +149,6 @@ final class Pending<Id, T> implements Result<Id, T> {
|
||||
|
||||
}
|
||||
|
||||
interface IdResult {}
|
||||
// this just wrapper, no need for this really
|
||||
final class IdFinal implements IdResult {
|
||||
final Value value;
|
||||
public IdFinal(Value value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
IdFinal that = (IdFinal)o;
|
||||
|
||||
if (value != that.value) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return value.ordinal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
final class IdPending implements IdResult {
|
||||
final IntIdComponent[] delta;
|
||||
|
||||
IdPending(IntIdComponent[] delta) {
|
||||
this.delta = delta;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof IdPending)) return false;
|
||||
IdPending pending = (IdPending)o;
|
||||
return Arrays.equals(delta, pending.delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(delta);
|
||||
}
|
||||
|
||||
IdPending copy() {
|
||||
IntIdComponent[] delta1 = new IntIdComponent[delta.length];
|
||||
for (int i = 0; i < delta.length; i++) {
|
||||
delta1[i] = delta[i].copy();
|
||||
}
|
||||
return new IdPending(delta1);
|
||||
}
|
||||
}
|
||||
|
||||
final class IdEquation {
|
||||
final long id;
|
||||
final IdResult rhs;
|
||||
|
||||
IdEquation(long id, IdResult rhs) {
|
||||
this.id = id;
|
||||
this.rhs = rhs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof IdEquation)) return false;
|
||||
|
||||
IdEquation equation = (IdEquation)o;
|
||||
|
||||
if (id != equation.id) return false;
|
||||
if (!rhs.equals(equation.rhs)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 31 * ((int)(id ^ (id >>> 32))) + rhs.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
final class Solution<Id, Val> {
|
||||
final Id id;
|
||||
final Val value;
|
||||
@@ -352,10 +178,10 @@ final class Solver {
|
||||
|
||||
private int size = 0;
|
||||
private final ELattice<Value> lattice;
|
||||
private final TLongObjectHashMap<TLongHashSet> dependencies = new TLongObjectHashMap<TLongHashSet>();
|
||||
private final TLongObjectHashMap<IdPending> pending = new TLongObjectHashMap<IdPending>();
|
||||
private final TLongObjectHashMap<Value> solved = new TLongObjectHashMap<Value>();
|
||||
private final LongStack moving = new LongStack();
|
||||
private final HashMap<HKey, HashSet<HKey>> dependencies = new HashMap<HKey, HashSet<HKey>>();
|
||||
private final HashMap<HKey, HPending> pending = new HashMap<HKey, HPending>();
|
||||
private final HashMap<HKey, Value> solved = new HashMap<HKey, Value>();
|
||||
private final Stack<HKey> moving = new Stack<HKey>();
|
||||
|
||||
int getSize() {
|
||||
return size;
|
||||
@@ -365,65 +191,67 @@ final class Solver {
|
||||
this.lattice = lattice;
|
||||
}
|
||||
|
||||
void addEquation(IdEquation equation) {
|
||||
private final HashSet<HKey> queued = new HashSet<HKey>();
|
||||
|
||||
void addEquation(HEquation equation) {
|
||||
size ++;
|
||||
IdResult rhs = equation.rhs;
|
||||
if (rhs instanceof IdFinal) {
|
||||
solved.put(equation.id, ((IdFinal) rhs).value);
|
||||
moving.push(equation.id);
|
||||
} else if (rhs instanceof IdPending) {
|
||||
IdPending pendResult = ((IdPending)rhs).copy();
|
||||
IdResult norm = normalize(pendResult.delta);
|
||||
if (norm instanceof IdFinal) {
|
||||
solved.put(equation.id, ((IdFinal) norm).value);
|
||||
moving.push(equation.id);
|
||||
HResult rhs = equation.result;
|
||||
if (!queued.add(equation.key)) {
|
||||
ProjectBytecodeAnalysis.LOG.error("duplicate");
|
||||
}
|
||||
if (rhs instanceof HFinal) {
|
||||
solved.put(equation.key, ((HFinal) rhs).value);
|
||||
moving.push(equation.key);
|
||||
} else if (rhs instanceof HPending) {
|
||||
HPending pendResult = ((HPending)rhs).copy();
|
||||
HResult norm = normalize(pendResult.delta);
|
||||
if (norm instanceof HFinal) {
|
||||
solved.put(equation.key, ((HFinal) norm).value);
|
||||
moving.push(equation.key);
|
||||
}
|
||||
else {
|
||||
IdPending pendResult1 = ((IdPending)rhs).copy();
|
||||
for (IntIdComponent component : pendResult1.delta) {
|
||||
for (long trigger : component.ids) {
|
||||
TLongHashSet set = dependencies.get(trigger);
|
||||
HPending pendResult1 = ((HPending)rhs).copy();
|
||||
for (HComponent component : pendResult1.delta) {
|
||||
for (HKey trigger : component.ids) {
|
||||
HashSet<HKey> set = dependencies.get(trigger);
|
||||
if (set == null) {
|
||||
set = new TLongHashSet();
|
||||
set = new HashSet<HKey>();
|
||||
dependencies.put(trigger, set);
|
||||
}
|
||||
set.add(equation.id);
|
||||
set.add(equation.key);
|
||||
}
|
||||
pending.put(equation.id, pendResult1);
|
||||
pending.put(equation.key, pendResult1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TLongObjectHashMap<Value> solve() {
|
||||
HashMap<HKey, Value> solve() {
|
||||
while (!moving.empty()) {
|
||||
long id = moving.pop();
|
||||
HKey id = moving.pop();
|
||||
Value value = solved.get(id);
|
||||
|
||||
boolean stable = id > 0;
|
||||
long[] pIds = stable ? new long[]{id, -id} : new long[]{-id, id};
|
||||
Value[] pVals = stable ? new Value[]{value, value} : new Value[]{value, lattice.top};
|
||||
HKey[] pIds = id.stable ? new HKey[]{id, id.negate()} : new HKey[]{id.negate(), id};
|
||||
Value[] pVals = id.stable ? new Value[]{value, value} : new Value[]{value, lattice.top};
|
||||
|
||||
for (int i = 0; i < pIds.length; i++) {
|
||||
long pId = pIds[i];
|
||||
HKey pId = pIds[i];
|
||||
Value pVal = pVals[i];
|
||||
TLongHashSet dIds = dependencies.get(pId);
|
||||
HashSet<HKey> dIds = dependencies.get(pId);
|
||||
if (dIds == null) {
|
||||
continue;
|
||||
}
|
||||
TLongIterator dIdsIterator = dIds.iterator();
|
||||
while (dIdsIterator.hasNext()) {
|
||||
long dId = dIdsIterator.next();
|
||||
IdPending pend = pending.remove(dId);
|
||||
for (HKey dId : dIds) {
|
||||
HPending pend = pending.remove(dId);
|
||||
if (pend != null) {
|
||||
IdResult pend1 = substitute(pend, pId, pVal);
|
||||
if (pend1 instanceof IdFinal) {
|
||||
IdFinal fi = (IdFinal)pend1;
|
||||
HResult pend1 = substitute(pend, pId, pVal);
|
||||
if (pend1 instanceof HFinal) {
|
||||
HFinal fi = (HFinal)pend1;
|
||||
solved.put(dId, fi.value);
|
||||
moving.push(dId);
|
||||
}
|
||||
else {
|
||||
pending.put(dId, (IdPending)pend1);
|
||||
pending.put(dId, (HPending)pend1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -434,9 +262,9 @@ final class Solver {
|
||||
}
|
||||
|
||||
// substitute id -> value into pending
|
||||
IdResult substitute(IdPending pending, long id, Value value) {
|
||||
IntIdComponent[] sum = pending.delta;
|
||||
for (IntIdComponent intIdComponent : sum) {
|
||||
HResult substitute(@NotNull HPending pending, @NotNull HKey id, @NotNull Value value) {
|
||||
HComponent[] sum = pending.delta;
|
||||
for (HComponent intIdComponent : sum) {
|
||||
if (intIdComponent.remove(id)) {
|
||||
intIdComponent.value = lattice.meet(intIdComponent.value, value);
|
||||
}
|
||||
@@ -444,17 +272,17 @@ final class Solver {
|
||||
return normalize(sum);
|
||||
}
|
||||
|
||||
IdResult normalize(IntIdComponent[] sum) {
|
||||
@NotNull HResult normalize(@NotNull HComponent[] sum) {
|
||||
Value acc = lattice.bot;
|
||||
boolean computableNow = true;
|
||||
for (IntIdComponent prod : sum) {
|
||||
for (HComponent prod : sum) {
|
||||
if (prod.isEmpty() || prod.value == lattice.bot) {
|
||||
acc = lattice.join(acc, prod.value);
|
||||
} else {
|
||||
computableNow = false;
|
||||
}
|
||||
}
|
||||
return (acc == lattice.top || computableNow) ? new IdFinal(acc) : new IdPending(sum);
|
||||
return (acc == lattice.top || computableNow) ? new HFinal(acc) : new HPending(sum);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-9
@@ -38,7 +38,7 @@ import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
|
||||
import com.intellij.util.AsynchConsumer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -50,7 +50,7 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
|
||||
private InferredAnnotationsManager myInferredAnnotationsManager;
|
||||
private ExternalAnnotationsManager myExternalAnnotationsManager;
|
||||
|
||||
private MessageDigest myMessageDigest;
|
||||
private List<String> diffs = new ArrayList<String>();
|
||||
|
||||
@Override
|
||||
@@ -62,6 +62,7 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
|
||||
myInferredAnnotationsManager = InferredAnnotationsManager.getInstance(myModule.getProject());
|
||||
myExternalAnnotationsManager = ExternalAnnotationsManager.getInstance(myModule.getProject());
|
||||
myMessageDigest = BytecodeAnalysisConverter.getMessageDigest();
|
||||
}
|
||||
|
||||
private void setUpLibraries() {
|
||||
@@ -127,13 +128,9 @@ public class BytecodeAnalysisIntegrationTest extends JavaCodeInsightFixtureTestC
|
||||
}
|
||||
|
||||
private void checkMethodAnnotations(PsiMethod method) {
|
||||
try {
|
||||
if (ProjectBytecodeAnalysis.getKey(method) == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
fail();
|
||||
|
||||
if (ProjectBytecodeAnalysis.getKey(method, myMessageDigest) == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// not null-result
|
||||
|
||||
+14
-9
@@ -37,6 +37,7 @@ import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
@@ -47,15 +48,15 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
private final String myClassesProjectRelativePath = "/classes/" + Test01.class.getPackage().getName().replace('.', '/');
|
||||
private JavaPsiFacade myJavaPsiFacade;
|
||||
private InferredAnnotationsManager myInferredAnnotationsManager;
|
||||
private BytecodeAnalysisConverter myBytecodeAnalysisConverter;
|
||||
private MessageDigest myMessageDigest;
|
||||
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
myJavaPsiFacade = JavaPsiFacade.getInstance(myModule.getProject());
|
||||
myInferredAnnotationsManager = InferredAnnotationsManager.getInstance(myModule.getProject());
|
||||
myBytecodeAnalysisConverter = BytecodeAnalysisConverter.getInstance();
|
||||
|
||||
myMessageDigest = MessageDigest.getInstance("MD5");
|
||||
setUpDataClasses();
|
||||
}
|
||||
|
||||
@@ -183,17 +184,21 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
|
||||
|
||||
private void checkCompoundId(Method method, PsiMethod psiMethod, boolean noKey) throws IOException {
|
||||
Direction direction = new Out();
|
||||
long psiKey = myBytecodeAnalysisConverter.mkPsiKey(psiMethod, direction);
|
||||
System.out.println();
|
||||
System.out.println(method.internalClassName);
|
||||
System.out.println(method.methodName);
|
||||
System.out.println(method.methodDesc);
|
||||
|
||||
|
||||
HKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, direction, myMessageDigest);
|
||||
if (noKey) {
|
||||
assertTrue(-1 == psiKey);
|
||||
assertTrue(null == psiKey);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
assertFalse(-1 == psiKey);
|
||||
assertFalse(null == psiKey);
|
||||
}
|
||||
|
||||
long asmKey = myBytecodeAnalysisConverter.mkAsmKey(new Key(method, direction, true));
|
||||
|
||||
HKey asmKey = BytecodeAnalysisConverter.asmKey(new Key(method, direction, true), myMessageDigest);
|
||||
Assert.assertEquals(asmKey, psiKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
<component>
|
||||
<implementation-class>com.intellij.util.xml.impl.JavaDomApplicationComponent</implementation-class>
|
||||
</component>
|
||||
<component>
|
||||
<interface-class>com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter</interface-class>
|
||||
<implementation-class>com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverterImpl</implementation-class>
|
||||
</component>
|
||||
</application-components>
|
||||
|
||||
<project-components>
|
||||
|
||||
Reference in New Issue
Block a user