bytecode analysis: hash based keys

This commit is contained in:
Ilya Klyuchnikov
2014-08-19 09:04:16 +04:00
parent 315e24fe1c
commit 35e6a586d0
11 changed files with 708 additions and 846 deletions
@@ -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;
@@ -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);
}
}
}
@@ -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();
}
}
}
@@ -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);
}
}
@@ -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 {}
@@ -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);
}
}
@@ -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
@@ -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);
}
-4
View File
@@ -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>