BytecodeAnalysis refactoring: HKey and Key merged to EKey; hashing is encapsulated inside HMethod/Method pair

This commit is contained in:
Tagir Valeev
2017-05-30 13:16:31 +07:00
parent 016bb9866b
commit 97eaee737b
31 changed files with 662 additions and 1010 deletions

View File

@@ -69,8 +69,8 @@ class AbstractValues {
}
}
static final class CallResultValue extends BasicValue {
final Set<Key> inters;
CallResultValue(Type tp, Set<Key> inters) {
final Set<EKey> inters;
CallResultValue(Type tp, Set<EKey> inters) {
super(tp);
this.inters = inters;
}
@@ -161,8 +161,8 @@ class AbstractValues {
static boolean equiv(BasicValue curr, BasicValue prev) {
if (curr.getClass() == prev.getClass()) {
if (curr instanceof CallResultValue && prev instanceof CallResultValue) {
Set<Key> keys1 = ((CallResultValue)prev).inters;
Set<Key> keys2 = ((CallResultValue)curr).inters;
Set<EKey> keys1 = ((CallResultValue)prev).inters;
Set<EKey> keys2 = ((CallResultValue)curr).inters;
return keys1.equals(keys2);
}
else return true;
@@ -220,7 +220,7 @@ abstract class Analysis<Res> {
final DFSTree dfsTree;
final protected List<State>[] computed;
final Key aKey;
final EKey aKey;
Res earlyResult;
@@ -231,7 +231,7 @@ abstract class Analysis<Res> {
methodNode = controlFlow.methodNode;
method = new Method(controlFlow.className, methodNode.name, methodNode.desc);
dfsTree = richControlFlow.dfsTree;
aKey = new Key(method, direction, stable);
aKey = new EKey(method, direction, stable);
computed = (List<State>[]) new List[controlFlow.transitions.length];
}

View File

@@ -17,7 +17,6 @@ package com.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint;
import com.intellij.codeInspection.dataFlow.StandardMethodContract;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.ThreadLocalCachedValue;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.psi.*;
@@ -66,80 +65,11 @@ public class BytecodeAnalysisConverter {
}
/**
* Converts an equation over asm keys into equation over small hash keys.
*/
@NotNull
static DirectionResultPair convert(@NotNull Equation equation, @NotNull MessageDigest md) {
ProgressManager.checkCanceled();
Result rhs = equation.rhs;
HResult hResult;
if (rhs instanceof Final) {
hResult = new HFinal(((Final)rhs).value);
}
else if (rhs instanceof Pending) {
Pending pending = (Pending)rhs;
Set<Product> sumOrigin = pending.sum;
HComponent[] components = new HComponent[sumOrigin.size()];
int componentI = 0;
for (Product prod : sumOrigin) {
HKey[] intProd = new HKey[prod.ids.size()];
int idI = 0;
for (Key key : prod.ids) {
intProd[idI] = asmKey(key, md);
idI++;
}
HComponent intIdComponent = new HComponent(prod.value, intProd);
components[componentI] = intIdComponent;
componentI++;
}
hResult = new HPending(components);
} else {
Effects wrapper = (Effects)rhs;
Set<EffectQuantum> effects = wrapper.effects;
Set<HEffectQuantum> hEffects = new HashSet<>();
for (EffectQuantum effect : effects) {
if (effect == EffectQuantum.TopEffectQuantum) {
hEffects.add(HEffectQuantum.TopEffectQuantum);
}
else if (effect == EffectQuantum.ThisChangeQuantum) {
hEffects.add(HEffectQuantum.ThisChangeQuantum);
}
else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
EffectQuantum.ParamChangeQuantum paramChangeQuantum = (EffectQuantum.ParamChangeQuantum)effect;
hEffects.add(new HEffectQuantum.ParamChangeQuantum(paramChangeQuantum.n));
}
else if (effect instanceof EffectQuantum.CallQuantum) {
EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect;
hEffects.add(new HEffectQuantum.CallQuantum(asmKey(callQuantum.key, md), callQuantum.data, callQuantum.isStatic));
}
}
hResult = new HEffects(hEffects);
}
return new DirectionResultPair(equation.id.direction.asInt(), hResult);
}
/**
* Converts an asm method key to a small hash key (HKey)
*/
@NotNull
public static HKey asmKey(@NotNull Key key, @NotNull MessageDigest md) {
byte[] classDigest = md.digest(key.method.internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET));
md.update(key.method.methodName.getBytes(CharsetToolkit.UTF8_CHARSET));
md.update(key.method.methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET));
byte[] sigDigest = md.digest();
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, key.direction.asInt(), key.stable, key.negated);
}
/**
* Converts a Psi method to a small hash key (HKey).
* Converts a Psi method to a small hash key (Key).
* Returns null if conversion is impossible (something is not resolvable).
*/
@Nullable
public static HKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) {
public static EKey psiKey(@NotNull PsiMethod psiMethod, @NotNull Direction direction, @NotNull MessageDigest md) {
final PsiClass psiClass = PsiTreeUtil.getParentOfType(psiMethod, PsiClass.class, false);
if (psiClass == null) {
return null;
@@ -155,7 +85,7 @@ public class BytecodeAnalysisConverter {
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, direction.asInt(), true, false);
return new EKey(new HMethod(digest), direction, true, false);
}
@Nullable
@@ -317,16 +247,16 @@ public class BytecodeAnalysisConverter {
/**
* Given a PSI method and its primary HKey enumerate all contract keys for it.
* Given a PSI method and its primary Key enumerate all contract keys for it.
*
* @param psiMethod psi method
* @param primaryKey primary stable keys
* @return corresponding (stable!) keys
*/
@NotNull
public static ArrayList<HKey> mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull HKey primaryKey) {
public static ArrayList<EKey> mkInOutKeys(@NotNull PsiMethod psiMethod, @NotNull EKey primaryKey) {
PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
ArrayList<HKey> keys = new ArrayList<>(parameters.length * 2 + 2);
ArrayList<EKey> keys = new ArrayList<>(parameters.length * 2 + 2);
keys.add(primaryKey);
for (int i = 0; i < parameters.length; i++) {
if (!(parameters[i].getType() instanceof PsiPrimitiveType)) {
@@ -352,21 +282,21 @@ public class BytecodeAnalysisConverter {
* @param methodKey a primary key of a method being analyzed. not it is stable
* @param arity arity of this method (hint for constructing @Contract annotations)
*/
public static void addMethodAnnotations(@NotNull Map<HKey, Value> solution, @NotNull MethodAnnotations methodAnnotations, @NotNull HKey methodKey, int arity) {
public static void addMethodAnnotations(@NotNull Map<EKey, Value> solution, @NotNull MethodAnnotations methodAnnotations, @NotNull EKey methodKey, int arity) {
List<StandardMethodContract> contractClauses = new ArrayList<>();
Set<HKey> notNulls = methodAnnotations.notNulls;
Set<HKey> pures = methodAnnotations.pures;
Map<HKey, String> contracts = methodAnnotations.contractsValues;
Set<EKey> notNulls = methodAnnotations.notNulls;
Set<EKey> pures = methodAnnotations.pures;
Map<EKey, String> contracts = methodAnnotations.contractsValues;
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
for (Map.Entry<EKey, Value> entry : solution.entrySet()) {
// NB: keys from Psi are always stable, so we need to stabilize keys from equations
Value value = entry.getValue();
if (value == Value.Top || value == Value.Bot || (value == Value.Fail && !pures.contains(methodKey))) {
continue;
}
HKey key = entry.getKey().mkStable();
EKey key = entry.getKey().mkStable();
Direction direction = key.getDirection();
HKey baseKey = key.mkBase();
EKey baseKey = key.mkBase();
if (!methodKey.equals(baseKey)) {
continue;
}
@@ -433,18 +363,18 @@ public class BytecodeAnalysisConverter {
return contractClauses;
}
public static void addEffectAnnotations(Map<HKey, Set<HEffectQuantum>> puritySolutions,
public static void addEffectAnnotations(Map<EKey, Set<EffectQuantum>> puritySolutions,
MethodAnnotations result,
HKey methodKey,
EKey methodKey,
boolean constructor) {
for (Map.Entry<HKey, Set<HEffectQuantum>> entry : puritySolutions.entrySet()) {
Set<HEffectQuantum> effects = entry.getValue();
HKey key = entry.getKey().mkStable();
HKey baseKey = key.mkBase();
for (Map.Entry<EKey, Set<EffectQuantum>> entry : puritySolutions.entrySet()) {
Set<EffectQuantum> effects = entry.getValue();
EKey key = entry.getKey().mkStable();
EKey baseKey = key.mkBase();
if (!methodKey.equals(baseKey)) {
continue;
}
if (effects.isEmpty() || (constructor && effects.size() == 1 && effects.contains(HEffectQuantum.ThisChangeQuantum))) {
if (effects.isEmpty() || (constructor && effects.size() == 1 && effects.contains(EffectQuantum.ThisChangeQuantum))) {
// Pure constructor is allowed to change "this" object as this is a new object anyways
result.pures.add(methodKey);
}

View File

@@ -49,21 +49,21 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
/**
* @author lambdamix
*/
public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
private static final ID<Bytes, Void> NAME = ID.create("bytecodeAnalysis");
public class BytecodeAnalysisIndex extends ScalarIndexExtension<HMethod> {
private static final ID<HMethod, Void> NAME = ID.create("bytecodeAnalysis");
private static final HKeyDescriptor KEY_DESCRIPTOR = new HKeyDescriptor();
private static final VirtualFileGist<Map<Bytes, HEquations>> ourGist = GistManager.getInstance().newVirtualFileGist(
"BytecodeAnalysisIndex", 5, new HEquationsExternalizer(), new ClassDataIndexer());
private static final VirtualFileGist<Map<HMethod, Equations>> ourGist = GistManager.getInstance().newVirtualFileGist(
"BytecodeAnalysisIndex", 7, new EquationsExternalizer(), new ClassDataIndexer());
@NotNull
@Override
public ID<Bytes, Void> getName() {
public ID<HMethod, Void> getName() {
return NAME;
}
@NotNull
@Override
public DataIndexer<Bytes, Void, FileContent> getIndexer() {
public DataIndexer<HMethod, Void, FileContent> getIndexer() {
return inputData -> {
try {
return collectKeys(inputData.getContent());
@@ -81,14 +81,14 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
}
@NotNull
private static Map<Bytes, Void> collectKeys(byte[] content) throws NoSuchAlgorithmException {
HashMap<Bytes, Void> map = new HashMap<>();
private static Map<HMethod, Void> collectKeys(byte[] content) throws NoSuchAlgorithmException {
HashMap<HMethod, Void> map = new HashMap<>();
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
new ClassReader(content).accept(new KeyedMethodVisitor() {
@Nullable
@Override
MethodVisitor visitMethod(MethodNode node, Key key) {
map.put(ClassDataIndexer.compressKey(md, key), null);
MethodVisitor visitMethod(MethodNode node, Method method, EKey key) {
map.put(method.hashed(md), null);
return null;
}
}, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
@@ -97,7 +97,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
@NotNull
@Override
public KeyDescriptor<Bytes> getKeyDescriptor() {
public KeyDescriptor<HMethod> getKeyDescriptor() {
return KEY_DESCRIPTOR;
}
@@ -123,7 +123,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
}
@NotNull
static List<HEquations> getEquations(GlobalSearchScope scope, Bytes key) {
static List<Equations> getEquations(GlobalSearchScope scope, HMethod key) {
Project project = ProjectManager.getInstance().getDefaultProject(); // the data is project-independent
return ContainerUtil.mapNotNull(FileBasedIndex.getInstance().getContainingFiles(NAME, key, scope),
file -> ourGist.getFileData(project, file).get(key));
@@ -132,37 +132,37 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
/**
* Externalizer for primary method keys.
*/
private static class HKeyDescriptor implements KeyDescriptor<Bytes>, DifferentSerializableBytesImplyNonEqualityPolicy {
private static class HKeyDescriptor implements KeyDescriptor<HMethod>, DifferentSerializableBytesImplyNonEqualityPolicy {
@Override
public void save(@NotNull DataOutput out, Bytes value) throws IOException {
out.write(value.bytes);
public void save(@NotNull DataOutput out, HMethod value) throws IOException {
out.write(value.myBytes);
}
@Override
public Bytes read(@NotNull DataInput in) throws IOException {
public HMethod read(@NotNull DataInput in) throws IOException {
byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
in.readFully(bytes);
return new Bytes(bytes);
return new HMethod(bytes);
}
@Override
public int getHashCode(Bytes value) {
return Arrays.hashCode(value.bytes);
public int getHashCode(HMethod value) {
return value.hashCode();
}
@Override
public boolean isEqual(Bytes val1, Bytes val2) {
return Arrays.equals(val1.bytes, val2.bytes);
public boolean isEqual(HMethod val1, HMethod val2) {
return val1.equals(val2);
}
}
/**
* Externalizer for compressed equations.
*/
public static class HEquationsExternalizer implements DataExternalizer<Map<Bytes, HEquations>> {
public static class EquationsExternalizer implements DataExternalizer<Map<HMethod, Equations>> {
@Override
public void save(@NotNull DataOutput out, Map<Bytes, HEquations> value) throws IOException {
public void save(@NotNull DataOutput out, Map<HMethod, Equations> value) throws IOException {
DataInputOutputUtilRt.writeSeq(out, value.entrySet(), entry -> {
KEY_DESCRIPTOR.save(out, entry.getKey());
saveEquations(out, entry.getValue());
@@ -170,53 +170,54 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
}
@Override
public Map<Bytes, HEquations> read(@NotNull DataInput in) throws IOException {
public Map<HMethod, Equations> read(@NotNull DataInput in) throws IOException {
return DataInputOutputUtilRt.readSeq(in, () -> Pair.create(KEY_DESCRIPTOR.read(in), readEquations(in))).
stream().collect(Collectors.toMap(p -> p.getFirst(), p -> p.getSecond()));
}
private static void saveEquations(@NotNull DataOutput out, HEquations eqs) throws IOException {
private static void saveEquations(@NotNull DataOutput out, Equations eqs) throws IOException {
out.writeBoolean(eqs.stable);
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
DataInputOutputUtil.writeINT(out, eqs.results.size());
for (DirectionResultPair pair : eqs.results) {
DataInputOutputUtil.writeINT(out, pair.directionKey);
HResult rhs = pair.hResult;
if (rhs instanceof HFinal) {
HFinal finalResult = (HFinal)rhs;
Result rhs = pair.hResult;
if (rhs instanceof Final) {
Final finalResult = (Final)rhs;
out.writeBoolean(true); // final flag
DataInputOutputUtil.writeINT(out, finalResult.value.ordinal());
}
else if (rhs instanceof HPending) {
HPending pendResult = (HPending)rhs;
else if (rhs instanceof Pending) {
Pending pendResult = (Pending)rhs;
out.writeBoolean(false); // pending flag
DataInputOutputUtil.writeINT(out, pendResult.delta.length);
for (HComponent component : pendResult.delta) {
for (Component component : pendResult.delta) {
DataInputOutputUtil.writeINT(out, component.value.ordinal());
HKey[] ids = component.ids;
EKey[] ids = component.ids;
DataInputOutputUtil.writeINT(out, ids.length);
for (HKey hKey : ids) {
out.write(hKey.key);
for (EKey hKey : ids) {
out.write(hKey.method.hashed(md).myBytes);
int rawDirKey = hKey.negated ? -hKey.dirKey : hKey.dirKey;
DataInputOutputUtil.writeINT(out, rawDirKey);
out.writeBoolean(hKey.stable);
}
}
}
else if (rhs instanceof HEffects) {
HEffects effects = (HEffects)rhs;
else if (rhs instanceof Effects) {
Effects effects = (Effects)rhs;
DataInputOutputUtil.writeINT(out, effects.effects.size());
for (HEffectQuantum effect : effects.effects) {
if (effect == HEffectQuantum.TopEffectQuantum) {
for (EffectQuantum effect : effects.effects) {
if (effect == EffectQuantum.TopEffectQuantum) {
DataInputOutputUtil.writeINT(out, -1);
}
else if (effect == HEffectQuantum.ThisChangeQuantum) {
else if (effect == EffectQuantum.ThisChangeQuantum) {
DataInputOutputUtil.writeINT(out, -2);
}
else if (effect instanceof HEffectQuantum.CallQuantum) {
else if (effect instanceof EffectQuantum.CallQuantum) {
DataInputOutputUtil.writeINT(out, -3);
HEffectQuantum.CallQuantum callQuantum = (HEffectQuantum.CallQuantum)effect;
out.write(callQuantum.key.key);
EffectQuantum.CallQuantum callQuantum = (EffectQuantum.CallQuantum)effect;
out.write(callQuantum.key.method.hashed(md).myBytes);
DataInputOutputUtil.writeINT(out, callQuantum.key.dirKey);
out.writeBoolean(callQuantum.key.stable);
out.writeBoolean(callQuantum.isStatic);
@@ -242,15 +243,15 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
}
}
}
else if (effect instanceof HEffectQuantum.ParamChangeQuantum) {
DataInputOutputUtil.writeINT(out, ((HEffectQuantum.ParamChangeQuantum)effect).n);
else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
DataInputOutputUtil.writeINT(out, ((EffectQuantum.ParamChangeQuantum)effect).n);
}
}
}
}
}
private static HEquations readEquations(@NotNull DataInput in) throws IOException {
private static Equations readEquations(@NotNull DataInput in) throws IOException {
boolean stable = in.readBoolean();
int size = DataInputOutputUtil.readINT(in);
ArrayList<DirectionResultPair> results = new ArrayList<>(size);
@@ -258,22 +259,22 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
int directionKey = DataInputOutputUtil.readINT(in);
Direction direction = Direction.fromInt(directionKey);
if (direction == Direction.Pure) {
Set<HEffectQuantum> effects = new HashSet<>();
Set<EffectQuantum> effects = new HashSet<>();
int effectsSize = DataInputOutputUtil.readINT(in);
for (int i = 0; i < effectsSize; i++) {
int effectMask = DataInputOutputUtil.readINT(in);
if (effectMask == -1) {
effects.add(HEffectQuantum.TopEffectQuantum);
effects.add(EffectQuantum.TopEffectQuantum);
}
else if (effectMask == -2) {
effects.add(HEffectQuantum.ThisChangeQuantum);
effects.add(EffectQuantum.ThisChangeQuantum);
}
else if (effectMask == -3){
byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
in.readFully(bytes);
int rawDirKey = DataInputOutputUtil.readINT(in);
boolean isStable = in.readBoolean();
HKey key = new HKey(bytes, Math.abs(rawDirKey), isStable, false);
EKey key = new EKey(new HMethod(bytes), Math.abs(rawDirKey), isStable, false);
boolean isStatic = in.readBoolean();
int dataLength = DataInputOutputUtil.readINT(in);
DataValue[] data = new DataValue[dataLength];
@@ -298,43 +299,43 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
data[di] = new DataValue.ParameterDataValue(dataI);
}
}
effects.add(new HEffectQuantum.CallQuantum(key, data, isStatic));
effects.add(new EffectQuantum.CallQuantum(key, data, isStatic));
}
else {
effects.add(new HEffectQuantum.ParamChangeQuantum(effectMask));
effects.add(new EffectQuantum.ParamChangeQuantum(effectMask));
}
}
results.add(new DirectionResultPair(directionKey, new HEffects(effects)));
results.add(new DirectionResultPair(directionKey, new Effects(effects)));
}
else {
boolean isFinal = in.readBoolean(); // flag
if (isFinal) {
int ordinal = DataInputOutputUtil.readINT(in);
Value value = Value.values()[ordinal];
results.add(new DirectionResultPair(directionKey, new HFinal(value)));
results.add(new DirectionResultPair(directionKey, new Final(value)));
}
else {
int sumLength = DataInputOutputUtil.readINT(in);
HComponent[] components = new HComponent[sumLength];
Component[] components = new Component[sumLength];
for (int i = 0; i < sumLength; i++) {
int ordinal = DataInputOutputUtil.readINT(in);
Value value = Value.values()[ordinal];
int componentSize = DataInputOutputUtil.readINT(in);
HKey[] ids = new HKey[componentSize];
EKey[] ids = new EKey[componentSize];
for (int j = 0; j < componentSize; j++) {
byte[] bytes = new byte[BytecodeAnalysisConverter.HASH_SIZE];
in.readFully(bytes);
int rawDirKey = DataInputOutputUtil.readINT(in);
ids[j] = new HKey(bytes, Math.abs(rawDirKey), in.readBoolean(), rawDirKey < 0);
ids[j] = new EKey(new HMethod(bytes), Direction.fromInt(Math.abs(rawDirKey)), in.readBoolean(), rawDirKey < 0);
}
components[i] = new HComponent(value, ids);
components[i] = new Component(value, ids);
}
results.add(new DirectionResultPair(directionKey, new HPending(components)));
results.add(new DirectionResultPair(directionKey, new Pending(components)));
}
}
}
return new HEquations(results, stable);
return new Equations(results, stable);
}
}
}

View File

@@ -46,7 +46,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
*
* @author lambdamix
*/
public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Bytes, HEquations>> {
public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMethod, Equations>> {
public static final Final FINAL_TOP = new Final(Value.Top);
public static final Final FINAL_FAIL = new Final(Value.Fail);
@@ -56,12 +56,12 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
@Nullable
@Override
public Map<Bytes, HEquations> calcData(@NotNull Project project, @NotNull VirtualFile file) {
HashMap<Bytes, HEquations> map = new HashMap<>();
public Map<HMethod, Equations> calcData(@NotNull Project project, @NotNull VirtualFile file) {
HashMap<HMethod, Equations> map = new HashMap<>();
try {
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
Map<Key, List<Equation>> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl());
allEquations.forEach((methodKey, equations) -> map.put(compressKey(md, methodKey), convertEquations(md, methodKey, equations)));
Map<EKey, List<Equation>> allEquations = processClass(new ClassReader(file.contentsToByteArray(false)), file.getPresentableUrl());
allEquations.forEach((methodKey, equations) -> map.put(methodKey.method.hashed(md), convertEquations(methodKey, equations)));
}
catch (ProcessCanceledException e) {
throw e;
@@ -75,18 +75,13 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
}
@NotNull
static Bytes compressKey(MessageDigest md, Key methodKey) {
return new Bytes(BytecodeAnalysisConverter.asmKey(methodKey, md).key);
}
@NotNull
private static HEquations convertEquations(MessageDigest md, Key methodKey, List<Equation> rawMethodEquations) {
private static Equations convertEquations(EKey methodKey, List<Equation> rawMethodEquations) {
List<DirectionResultPair> compressedMethodEquations =
ContainerUtil.map(rawMethodEquations, equation -> BytecodeAnalysisConverter.convert(equation, md));
return new HEquations(compressedMethodEquations, methodKey.stable);
ContainerUtil.map(rawMethodEquations, equation -> new DirectionResultPair(equation.key.dirKey, equation.result));
return new Equations(compressedMethodEquations, methodKey.stable);
}
public static Map<Key, List<Equation>> processClass(final ClassReader classReader, final String presentableUrl) {
public static Map<EKey, List<Equation>> processClass(final ClassReader classReader, final String presentableUrl) {
// It is OK to share pending states, actions and results for analyses.
// Analyses are designed in such a way that they first write to states/actions/results and then read only those portion
@@ -95,11 +90,11 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
final State[] sharedPendingStates = new State[Analysis.STEPS_LIMIT];
final PendingAction[] sharedPendingActions = new PendingAction[Analysis.STEPS_LIMIT];
final PResults.PResult[] sharedResults = new PResults.PResult[Analysis.STEPS_LIMIT];
final Map<Key, List<Equation>> equations = new HashMap<>();
final Map<EKey, List<Equation>> equations = new HashMap<>();
classReader.accept(new KeyedMethodVisitor() {
protected MethodVisitor visitMethod(final MethodNode node, final Key key) {
protected MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key) {
return new MethodVisitor(Opcodes.API_VERSION, node) {
private boolean jsr;
@@ -114,7 +109,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
@Override
public void visitEnd() {
super.visitEnd();
equations.put(key, processMethod(node, jsr, key.method, key.stable));
equations.put(key, processMethod(node, jsr, method, key.stable));
}
};
}
@@ -133,9 +128,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
final boolean isBooleanResult = ASMUtils.isBooleanType(resultType);
final boolean isInterestingResult = isReferenceResult || isBooleanResult;
// 4*n: for each reference parameter: @NotNull IN, @Nullable, null -> ... contract, !null -> contract
// 3: @NotNull OUT, @Nullable OUT, purity analysis
List<Equation> equations = new ArrayList<>(argumentTypes.length * 4 + 3);
List<Equation> equations = new ArrayList<>();
equations.add(PurityAnalysis.analyze(method, methodNode, stable));
try {
@@ -300,14 +293,14 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
if (isReferenceResult) {
result.add(outEquation);
result.add(new Equation(new Key(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
result.add(new Equation(new EKey(method, NullableOut, stable), NullableMethodAnalysis.analyze(methodNode, origins, jsr)));
}
final boolean shouldInferNonTrivialFailingContracts;
final Equation throwEquation;
if(methodNode.name.equals("<init>")) {
// Do not infer failing contracts for constructors
shouldInferNonTrivialFailingContracts = false;
throwEquation = new Equation(new Key(method, Throw, stable), FINAL_TOP);
throwEquation = new Equation(new EKey(method, Throw, stable), FINAL_TOP);
} else {
final InThrowAnalysis inThrowAnalysis = new InThrowAnalysis(richControlFlow, Throw, origins, stable, sharedPendingStates);
throwEquation = inThrowAnalysis.analyze();
@@ -333,8 +326,8 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
}
if (shouldInferNonTrivialFailingContracts) {
InThrow direction = new InThrow(index, val);
if (throwEquation.rhs.equals(FINAL_FAIL)) {
builder.add(new Equation(new Key(method, direction, stable), FINAL_FAIL));
if (throwEquation.result.equals(FINAL_FAIL)) {
builder.add(new Equation(new EKey(method, direction, stable), FINAL_FAIL));
}
else {
builder.add(new InThrowAnalysis(richControlFlow, direction, origins, stable, sharedPendingStates).analyze());
@@ -354,39 +347,40 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
boolean possibleNPE = false;
if (leakingParameters[i]) {
NonNullInAnalysis notNullInAnalysis =
new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL_MASK), stable, sharedPendingActions, sharedResults);
new NonNullInAnalysis(richControlFlow, new In(i, false), stable, sharedPendingActions, sharedResults);
Equation notNullParamEquation = notNullInAnalysis.analyze();
possibleNPE = notNullInAnalysis.possibleNPE;
notNullParam = notNullParamEquation.rhs.equals(FINAL_NOT_NULL);
notNullParam = notNullParamEquation.result.equals(FINAL_NOT_NULL);
result.add(notNullParamEquation);
}
else {
// parameter is not leaking, so it is definitely NOT @NotNull
result.add(new Equation(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
result.add(new Equation(new EKey(method, new In(i, false), stable), FINAL_TOP));
}
if (leakingNullableParameters[i]) {
if (notNullParam || possibleNPE) {
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
result.add(new Equation(new EKey(method, new In(i, true), stable), FINAL_TOP));
}
else {
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE_MASK), stable, sharedPendingStates).analyze());
result.add(new NullableInAnalysis(richControlFlow, new In(i, true), stable, sharedPendingStates).analyze());
}
}
else {
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_NULL));
result.add(new Equation(new EKey(method, new In(i, true), stable), FINAL_NULL));
}
if (isInterestingResult) {
if (!leakingParameters[i]) {
// parameter is not leaking, so a contract is the same as for the whole method
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), outEquation.rhs));
result.add(new Equation(new Key(method, new InOut(i, Value.NotNull), stable), outEquation.rhs));
result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), outEquation.result));
result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), outEquation.result));
continue;
}
if (notNullParam) {
// @NotNull, like "null->fail"
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), FINAL_BOT));
result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), outEquation.result));
continue;
}
}
@@ -421,23 +415,23 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
}
private List<Equation> topEquations(Method method,
Type[] argumentTypes,
boolean isReferenceResult,
boolean isInterestingResult,
boolean stable) {
Type[] argumentTypes,
boolean isReferenceResult,
boolean isInterestingResult,
boolean stable) {
// 4 = @NotNull parameter, @Nullable parameter, null -> ..., !null -> ...
List<Equation> result = new ArrayList<>(argumentTypes.length * 4 + 2);
if (isReferenceResult) {
result.add(new Equation(new Key(method, Out, stable), FINAL_TOP));
result.add(new Equation(new Key(method, NullableOut, stable), FINAL_BOT));
result.add(new Equation(new EKey(method, Out, stable), FINAL_TOP));
result.add(new Equation(new EKey(method, NullableOut, stable), FINAL_BOT));
}
for (int i = 0; i < argumentTypes.length; i++) {
if (ASMUtils.isReferenceType(argumentTypes[i])) {
result.add(new Equation(new Key(method, new In(i, In.NOT_NULL_MASK), stable), FINAL_TOP));
result.add(new Equation(new Key(method, new In(i, In.NULLABLE_MASK), stable), FINAL_TOP));
result.add(new Equation(new EKey(method, new In(i, false), stable), FINAL_TOP));
result.add(new Equation(new EKey(method, new In(i, true), stable), FINAL_TOP));
if (isInterestingResult) {
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
result.add(new Equation(new Key(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
result.add(new Equation(new EKey(method, new InOut(i, Value.Null), stable), FINAL_TOP));
result.add(new Equation(new EKey(method, new InOut(i, Value.NotNull), stable), FINAL_TOP));
}
}
}

View File

@@ -104,14 +104,14 @@ interface CombinedData {
}
@NotNull
Set<Key> getKeysForParameter(int idx, ParamValueBasedDirection direction) {
Set<Key> keys = new HashSet<>();
Set<EKey> getKeysForParameter(int idx, ParamValueBasedDirection direction) {
Set<EKey> keys = new HashSet<>();
for (int argI = 0; argI < this.args.size(); argI++) {
BasicValue arg = this.args.get(argI);
if (arg instanceof NthParamValue) {
NthParamValue npv = (NthParamValue)arg;
if (npv.n == idx) {
keys.add(new Key(this.method, direction.withIndex(argI), this.stableCall));
keys.add(new EKey(this.method, direction.withIndex(argI), this.stableCall));
}
}
}
@@ -203,7 +203,7 @@ final class CombinedAnalysis {
}
final Equation notNullParamEquation(int i, boolean stable) {
final Key key = new Key(method, new In(i, In.NOT_NULL_MASK), stable);
final EKey key = new EKey(method, new In(i, false), stable);
final Result result;
if (interpreter.dereferencedParams[i]) {
result = new Final(Value.NotNull);
@@ -214,18 +214,18 @@ final class CombinedAnalysis {
result = new Final(Value.Top);
}
else {
Set<Key> keys = new HashSet<>();
Set<EKey> keys = new HashSet<>();
for (ParamKey pk: calls) {
keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL_MASK), pk.stable));
keys.add(new EKey(pk.method, new In(pk.i, false), pk.stable));
}
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
}
}
return new Equation(key, result);
}
final Equation nullableParamEquation(int i, boolean stable) {
final Key key = new Key(method, new In(i, In.NULLABLE_MASK), stable);
final EKey key = new EKey(method, new In(i, true), stable);
final Result result;
if (interpreter.dereferencedParams[i] || interpreter.notNullableParams[i] || returnValue instanceof NthParamValue && ((NthParamValue)returnValue).n == i) {
result = new Final(Value.Top);
@@ -236,9 +236,9 @@ final class CombinedAnalysis {
result = new Final(Value.Null);
}
else {
Set<Product> sum = new HashSet<>();
Set<Component> sum = new HashSet<>();
for (ParamKey pk: calls) {
sum.add(new Product(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE_MASK), pk.stable))));
sum.add(new Component(Value.Top, Collections.singleton(new EKey(pk.method, new In(pk.i, true), pk.stable))));
}
result = new Pending(sum);
}
@@ -249,7 +249,7 @@ final class CombinedAnalysis {
@Nullable
final Equation contractEquation(int i, Value inValue, boolean stable) {
final InOut direction = new InOut(i, inValue);
final Key key = new Key(method, direction, stable);
final EKey key = new EKey(method, direction, stable);
final Result result;
if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) {
result = new Final(Value.Bot);
@@ -271,14 +271,14 @@ final class CombinedAnalysis {
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
Set<Key> keys = call.getKeysForParameter(i, direction);
Set<EKey> keys = call.getKeysForParameter(i, direction);
if (ASMUtils.isReferenceType(call.getType())) {
keys.add(new Key(call.method, Out, call.stableCall));
keys.add(new EKey(call.method, Out, call.stableCall));
}
if (keys.isEmpty()) {
return null;
} else {
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
}
}
else {
@@ -289,15 +289,15 @@ final class CombinedAnalysis {
@Nullable
final Equation failEquation(boolean stable) {
final Key key = new Key(method, Throw, stable);
final EKey key = new EKey(method, Throw, stable);
final Result result;
if (exception) {
result = new Final(Value.Fail);
}
else if (!interpreter.calls.isEmpty()) {
Set<Key> keys =
interpreter.calls.stream().map(call -> new Key(call.method, Throw, call.stableCall)).collect(Collectors.toSet());
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
Set<EKey> keys =
interpreter.calls.stream().map(call -> new EKey(call.method, Throw, call.stableCall)).collect(Collectors.toSet());
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
}
else {
return null;
@@ -308,18 +308,18 @@ final class CombinedAnalysis {
@Nullable
final Equation failEquation(int i, Value inValue, boolean stable) {
final InThrow direction = new InThrow(i, inValue);
final Key key = new Key(method, direction, stable);
final EKey key = new EKey(method, direction, stable);
final Result result;
if (exception) {
result = new Final(Value.Fail);
}
else if (!interpreter.calls.isEmpty()) {
Set<Key> keys = new HashSet<>();
Set<EKey> keys = new HashSet<>();
for (TrackableCallValue call : interpreter.calls) {
keys.addAll(call.getKeysForParameter(i, direction));
keys.add(new Key(call.method, Throw, call.stableCall));
keys.add(new EKey(call.method, Throw, call.stableCall));
}
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
}
else {
return null;
@@ -329,7 +329,7 @@ final class CombinedAnalysis {
@Nullable
final Equation outContractEquation(boolean stable) {
final Key key = new Key(method, Out, stable);
final EKey key = new EKey(method, Out, stable);
final Result result;
if (exception) {
result = new Final(Value.Bot);
@@ -348,9 +348,9 @@ final class CombinedAnalysis {
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
Key callKey = new Key(call.method, Out, call.stableCall);
Set<Key> keys = new SingletonSet<>(callKey);
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
EKey callKey = new EKey(call.method, Out, call.stableCall);
Set<EKey> keys = new SingletonSet<>(callKey);
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
}
else {
return null;
@@ -359,7 +359,7 @@ final class CombinedAnalysis {
}
final Equation nullableResultEquation(boolean stable) {
final Key key = new Key(method, NullableOut, stable);
final EKey key = new EKey(method, NullableOut, stable);
final Result result;
if (exception ||
returnValue instanceof Trackable && interpreter.dereferencedValues[((Trackable)returnValue).getOriginInsnIndex()]) {
@@ -367,9 +367,9 @@ final class CombinedAnalysis {
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
Key callKey = new Key(call.method, NullableOut, call.stableCall || call.thisCall);
Set<Key> keys = new SingletonSet<>(callKey);
result = new Pending(new SingletonSet<>(new Product(Value.Null, keys)));
EKey callKey = new EKey(call.method, NullableOut, call.stableCall || call.thisCall);
Set<EKey> keys = new SingletonSet<>(callKey);
result = new Pending(new SingletonSet<>(new Component(Value.Null, keys)));
}
else if (returnValue instanceof TrackableNullValue) {
result = new Final(Value.Null);
@@ -735,22 +735,22 @@ final class NegationAnalysis {
}
final Equation contractEquation(int i, Value inValue, boolean stable) {
final Key key = new Key(method, new InOut(i, inValue), stable);
final EKey key = new EKey(method, new InOut(i, inValue), stable);
final Result result;
HashSet<Key> keys = new HashSet<>();
HashSet<EKey> keys = new HashSet<>();
for (int argI = 0; argI < conditionValue.args.size(); argI++) {
BasicValue arg = conditionValue.args.get(argI);
if (arg instanceof NthParamValue) {
NthParamValue npv = (NthParamValue)arg;
if (npv.n == i) {
keys.add(new Key(conditionValue.method, new InOut(argI, inValue), conditionValue.stableCall, true));
keys.add(new EKey(conditionValue.method, new InOut(argI, inValue), conditionValue.stableCall, true));
}
}
}
if (keys.isEmpty()) {
result = new Final(Value.Top);
} else {
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
result = new Pending(new SingletonSet<>(new Component(Value.Top, keys)));
}
return new Equation(key, result);
}

View File

@@ -29,7 +29,7 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
import org.jetbrains.org.objectweb.asm.tree.analysis.Frame;
import java.util.Collections;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -65,6 +65,16 @@ abstract class ContractAnalysis extends Analysis<Result> {
return new Equation(aKey, res);
}
static Result checkLimit(Result result) throws AnalyzerException {
if(result instanceof Pending) {
int size = Arrays.stream(((Pending)result).delta).mapToInt(prod -> prod.ids.length).sum();
if (size > Analysis.EQUATION_SIZE_LIMIT) {
throw new AnalyzerException(null, "Equation size is too big");
}
}
return result;
}
@NotNull
protected Equation analyze() throws AnalyzerException {
pendingPush(createStartState());
@@ -279,14 +289,14 @@ class InOutAnalysis extends ContractAnalysis {
subResult = new Final(inValue);
}
else if (stackTop instanceof CallResultValue) {
Set<Key> keys = ((CallResultValue) stackTop).inters;
subResult = new Pending(Collections.singleton(new Product(Value.Top, keys)));
Set<EKey> keys = ((CallResultValue) stackTop).inters;
subResult = new Pending(new Component[] {new Component(Value.Top, keys)});
}
else {
earlyResult = new Final(Value.Top);
return true;
}
internalResult = resultUtil.join(internalResult, subResult);
internalResult = checkLimit(resultUtil.join(internalResult, subResult));
if (internalResult instanceof Final && ((Final)internalResult).value == Value.Top) {
earlyResult = internalResult;
}
@@ -507,22 +517,22 @@ class InOutInterpreter extends BasicInterpreter {
boolean isRefRetType = retType.getSort() == Type.OBJECT || retType.getSort() == Type.ARRAY;
if (!Type.VOID_TYPE.equals(retType)) {
if (direction != null) {
HashSet<Key> keys = new HashSet<>();
HashSet<EKey> keys = new HashSet<>();
for (int i = shift; i < values.size(); i++) {
if (values.get(i) instanceof ParamValue) {
keys.add(new Key(method, direction.withIndex(i - shift), stable));
keys.add(new EKey(method, direction.withIndex(i - shift), stable));
}
}
if (isRefRetType) {
keys.add(new Key(method, Out, stable));
keys.add(new EKey(method, Out, stable));
}
if (!keys.isEmpty()) {
return new CallResultValue(retType, keys);
}
}
else if (isRefRetType) {
HashSet<Key> keys = new HashSet<>();
keys.add(new Key(method, Out, stable));
HashSet<EKey> keys = new HashSet<>();
keys.add(new EKey(method, Out, stable));
return new CallResultValue(retType, keys);
}
}

View File

@@ -19,19 +19,24 @@ import com.intellij.util.ArrayFactory;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Represents a lattice product of a constant {@link #value} and all {@link #ids}.
*/
final class HComponent {
static final HComponent[] EMPTY_ARRAY = new HComponent[0];
static final ArrayFactory<HComponent> ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new HComponent[count];
final class Component {
static final Component[] EMPTY_ARRAY = new Component[0];
static final ArrayFactory<Component> ARRAY_FACTORY = count -> count == 0 ? EMPTY_ARRAY : new Component[count];
@NotNull Value value;
@NotNull final HKey[] ids;
@NotNull final EKey[] ids;
HComponent(@NotNull Value value, @NotNull HKey[] ids) {
Component(@NotNull Value value, @NotNull Set<EKey> ids) {
this(value, ids.toArray(new EKey[0]));
}
Component(@NotNull Value value, @NotNull EKey[] ids) {
this.value = value;
this.ids = ids;
}
@@ -41,45 +46,17 @@ final class HComponent {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HComponent that = (HComponent)o;
Component that = (Component)o;
if (!Arrays.equals(ids, that.ids)) return false;
if (value != that.value) return false;
return true;
return value == that.value && Arrays.equals(ids, that.ids);
}
@Override
public int hashCode() {
int result = value.hashCode();
result = 31 * result + Arrays.hashCode(ids);
return result;
return 31 * value.hashCode() + Arrays.hashCode(ids);
}
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) {
public boolean remove(@NotNull EKey id) {
boolean removed = false;
for (int i = 0; i < ids.length; i++) {
if (id.equals(ids[i])) {
@@ -89,13 +66,25 @@ class HUtils {
}
return removed;
}
public boolean isEmpty() {
for (EKey id : ids) {
if (id != null) return false;
}
return true;
}
@NotNull
public Component copy() {
return new Component(value, ids.clone());
}
}
final class HEquation {
@NotNull final HKey key;
@NotNull final HResult result;
final class Equation {
@NotNull final EKey key;
@NotNull final Result result;
HEquation(@NotNull HKey key, @NotNull HResult result) {
Equation(@NotNull EKey key, @NotNull Result result) {
this.key = key;
this.result = result;
}
@@ -104,48 +93,26 @@ final class HEquation {
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;
Equation equation = (Equation)o;
return key.equals(equation.key) && result.equals(equation.result);
}
@Override
public int hashCode() {
int result1 = key.hashCode();
result1 = 31 * result1 + result.hashCode();
return result1;
}
}
/**
* Bytes of primary HKey of a method.
*/
final class Bytes {
@NotNull
final byte[] bytes;
Bytes(@NotNull byte[] bytes) {
this.bytes = bytes;
return 31 * key.hashCode() + result.hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return Arrays.equals(bytes, ((Bytes)o).bytes);
}
@Override
public int hashCode() {
return Arrays.hashCode(bytes);
public String toString() {
return "Equation{" + "key=" + key + ", result=" + result + '}';
}
}
class HEquations {
class Equations {
@NotNull final List<DirectionResultPair> results;
final boolean stable;
HEquations(@NotNull List<DirectionResultPair> results, boolean stable) {
Equations(@NotNull List<DirectionResultPair> results, boolean stable) {
this.results = results;
this.stable = stable;
}
@@ -155,28 +122,22 @@ class HEquations {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HEquations that = (HEquations)o;
if (stable != that.stable) return false;
if (!results.equals(that.results)) return false;
return true;
Equations that = (Equations)o;
return stable == that.stable && results.equals(that.results);
}
@Override
public int hashCode() {
int result = results.hashCode();
result = 31 * result + (stable ? 1 : 0);
return result;
return 31 * results.hashCode() + (stable ? 1 : 0);
}
}
class DirectionResultPair {
final int directionKey;
@NotNull
final HResult hResult;
final Result hResult;
DirectionResultPair(int directionKey, @NotNull HResult hResult) {
DirectionResultPair(int directionKey, @NotNull Result hResult) {
this.directionKey = directionKey;
this.hResult = hResult;
}
@@ -187,26 +148,20 @@ class DirectionResultPair {
if (o == null || getClass() != o.getClass()) return false;
DirectionResultPair that = (DirectionResultPair)o;
if (directionKey != that.directionKey) return false;
if (!hResult.equals(that.hResult)) return false;
return true;
return directionKey == that.directionKey && hResult.equals(that.hResult);
}
@Override
public int hashCode() {
int result = directionKey;
result = 31 * result + hResult.hashCode();
return result;
return 31 * directionKey + hResult.hashCode();
}
}
interface HResult {}
final class HFinal implements HResult {
interface Result {}
final class Final implements Result {
@NotNull final Value value;
HFinal(@NotNull Value value) {
Final(@NotNull Value value) {
this.value = value;
}
@@ -215,23 +170,28 @@ final class HFinal implements HResult {
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;
return value == ((Final)o).value;
}
@Override
public int hashCode() {
return value.ordinal();
}
@Override
public String toString() {
return "Final{" + "value=" + value + '}';
}
}
final class HPending implements HResult {
@NotNull final HComponent[] delta; // sum
final class Pending implements Result {
@NotNull final Component[] delta; // sum
HPending(@NotNull HComponent[] delta) {
Pending(Collection<Component> delta) {
this(delta.toArray(Component.EMPTY_ARRAY));
}
Pending(@NotNull Component[] delta) {
this.delta = delta;
}
@@ -239,9 +199,7 @@ final class HPending implements HResult {
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;
return Arrays.equals(delta, ((Pending)o).delta);
}
@Override
@@ -250,20 +208,19 @@ final class HPending implements HResult {
}
@NotNull
HPending copy() {
HComponent[] delta1 = new HComponent[delta.length];
Pending copy() {
Component[] copy = new Component[delta.length];
for (int i = 0; i < delta.length; i++) {
delta1[i] = delta[i].copy();
copy[i] = delta[i].copy();
}
return new HPending(delta1);
return new Pending(copy);
}
}
final class HEffects implements HResult {
@NotNull final Set<HEffectQuantum> effects;
final class Effects implements Result {
@NotNull final Set<EffectQuantum> effects;
HEffects(@NotNull Set<HEffectQuantum> effects) {
Effects(@NotNull Set<EffectQuantum> effects) {
this.effects = effects;
}
@@ -271,8 +228,7 @@ final class HEffects implements HResult {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HEffects hEffects = (HEffects)o;
return effects.equals(hEffects.effects);
return this.effects.equals(((Effects)o).effects);
}
@Override

View File

@@ -49,7 +49,7 @@ public abstract class Direction {
int subDirectionId = paramKey % DIRECTIONS_PER_PARAM_ID;
// 0 - 1 - @NotNull, @Nullable, parameter
if (subDirectionId < IN_OUT_OFFSET) {
return new In(paramId, subDirectionId);
return new In(paramId, subDirectionId == 1);
}
if (subDirectionId < IN_THROW_OFFSET) {
int valueId = subDirectionId - IN_OUT_OFFSET;
@@ -60,7 +60,7 @@ public abstract class Direction {
}
/**
* Encodes Direction object as int.
* Encodes Direction object as non-negative int.
*
* @return unique int for direction
*/
@@ -114,27 +114,21 @@ public abstract class Direction {
}
static final class In extends ParamIdBasedDirection {
static final int NOT_NULL_MASK = 0;
static final int NULLABLE_MASK = 1;
/**
* @see #NOT_NULL_MASK
* @see #NULLABLE_MASK
*/
final int nullityMask;
final boolean nullable;
In(int paramIndex, int nullityMask) {
In(int paramIndex, boolean nullable) {
super(paramIndex);
this.nullityMask = nullityMask;
this.nullable = nullable;
}
@Override
int asInt() {
return super.asInt() + nullityMask;
return super.asInt() + (nullable ? 1 : 0);
}
@Override
public String toString() {
return "In " + paramIndex;
return "In " + paramIndex + "(" + (nullable ? "nullable" : "not null") + ")";
}
}

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2000-2017 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.security.MessageDigest;
/**
* Equation key (or variable)
*/
public final class EKey {
@NotNull
final MethodDescriptor method;
final int dirKey;
final boolean stable;
final boolean negated;
public EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable) {
this(method, direction, stable, false);
}
EKey(@NotNull MethodDescriptor method, Direction direction, boolean stable, boolean negated) {
this(method, direction.asInt(), stable, negated);
}
EKey(@NotNull MethodDescriptor method, int dirKey, boolean stable, boolean negated) {
this.method = method;
this.dirKey = dirKey;
this.stable = stable;
this.negated = negated;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EKey key = (EKey) o;
if (stable != key.stable) return false;
if (negated != key.negated) return false;
if (dirKey != key.dirKey) return false;
if (!method.equals(key.method)) return false;
return true;
}
@Override
public int hashCode() {
int result = method.hashCode();
result = 31 * result + dirKey;
result = 31 * result + (stable ? 1 : 0);
result = 31 * result + (negated ? 1 : 0);
return result;
}
EKey invertStability() {
return new EKey(method, dirKey, !stable, negated);
}
EKey mkStable() {
return stable ? this : new EKey(method, dirKey, true, negated);
}
EKey mkUnstable() {
return stable ? new EKey(method, dirKey, false, negated) : this;
}
public EKey mkBase() {
return withDirection(Direction.Out);
}
EKey withDirection(Direction dir) {
return dirKey == dir.asInt() ? this : new EKey(method, dir, stable, false);
}
EKey negate() {
return new EKey(method, dirKey, stable, true);
}
public EKey hashed(MessageDigest md) {
HMethod hmethod = method.hashed(md);
return hmethod == method ? this : new EKey(hmethod, dirKey, stable, negated);
}
public Direction getDirection() {
return Direction.fromInt(dirKey);
}
@Override
public String toString() {
return "Key [" + method + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + Direction.fromInt(dirKey) + "]";
}
}

View File

@@ -1,99 +0,0 @@
/*
* Copyright 2000-2017 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 one.util.streamex.IntStreamEx;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* Small size key, constructed by hashing method signature.
* 'H' in this and related class names stands for 'Hash'.
* @see BytecodeAnalysisConverter for details of construction.
*/
public final class HKey {
@NotNull
final byte[] key;
final int dirKey;
final boolean stable;
final boolean negated;
HKey(@NotNull byte[] key, int dirKey, boolean stable, boolean negated) {
this.key = key;
this.dirKey = dirKey;
this.stable = stable;
this.negated = negated;
}
@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 (negated != hKey.negated) 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);
result = 31 * result + (negated ? 1 : 0);
return result;
}
HKey invertStability() {
return new HKey(key, dirKey, !stable, negated);
}
HKey mkStable() {
return stable ? this : new HKey(key, dirKey, true, negated);
}
HKey mkUnstable() {
return stable ? new HKey(key, dirKey, false, negated) : this;
}
public HKey mkBase() {
return dirKey == 0 ? this : new HKey(key, 0, stable, false);
}
HKey withDirection(Direction dir) {
return new HKey(key, dir.asInt(), stable, false);
}
HKey negate() {
return new HKey(key, dirKey, stable, true);
}
public Direction getDirection() {
return Direction.fromInt(dirKey);
}
@Override
public String toString() {
return "HKey [" + bytesToString(key) + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + getDirection() + "]";
}
static String bytesToString(byte[] key) {
return IntStreamEx.of(key).mapToObj(b -> String.format("%02x", b & 0xFF)).joining(".");
}
}

View File

@@ -15,49 +15,46 @@
*/
package com.intellij.codeInspection.bytecodeAnalysis;
public final class Key {
final Method method;
final Direction direction;
final boolean stable;
final boolean negated;
import one.util.streamex.IntStreamEx;
import org.jetbrains.annotations.NotNull;
public Key(Method method, Direction direction, boolean stable) {
this.method = method;
this.direction = direction;
this.stable = stable;
this.negated = false;
}
import java.security.MessageDigest;
import java.util.Arrays;
Key(Method method, Direction direction, boolean stable, boolean negated) {
this.method = method;
this.direction = direction;
this.stable = stable;
this.negated = negated;
/**
* Hashed representation of method.
*/
public final class HMethod implements MethodDescriptor {
@NotNull
final byte[] myBytes;
public HMethod(@NotNull byte[] bytes) {
myBytes = bytes;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!direction.equals(key.direction)) return false;
if (!method.equals(key.method)) return false;
if (stable != key.stable) return false;
return true;
return Arrays.equals(myBytes, ((HMethod)o).myBytes);
}
@Override
public int hashCode() {
int result = method.hashCode();
result = 31 * result + direction.hashCode();
result = 31 * result + (stable ? 1 : 0);
return result;
return Arrays.hashCode(myBytes);
}
@NotNull
@Override
public HMethod hashed(MessageDigest md) {
return this;
}
public String toString() {
return method + " " + direction + " " + stable;
return bytesToString(myBytes);
}
static String bytesToString(byte[] key) {
return IntStreamEx.of(key).mapToObj(b -> String.format("%02x", b & 0xFF)).joining(".");
}
}

View File

@@ -49,9 +49,9 @@ abstract class KeyedMethodVisitor extends ClassVisitor {
Method method = new Method(className, node.name, node.desc);
boolean stable = stableClass || (node.access & STABLE_FLAGS) != 0 || "<init>".equals(node.name);
return visitMethod(node, new Key(method, Out, stable));
return visitMethod(node, method, new EKey(method, Out, stable));
}
@Nullable
abstract MethodVisitor visitMethod(final MethodNode node, final Key key);
abstract MethodVisitor visitMethod(final MethodNode node, Method method, final EKey key);
}

View File

@@ -15,9 +15,16 @@
*/
package com.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.openapi.vfs.CharsetToolkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode;
public final class Method {
import java.security.MessageDigest;
import static com.intellij.codeInspection.bytecodeAnalysis.BytecodeAnalysisConverter.*;
public final class Method implements MethodDescriptor {
final String internalClassName;
final String methodName;
final String methodDesc;
@@ -62,6 +69,22 @@ public final class Method {
this.methodDesc = mNode.desc;
}
@NotNull
@Override
public HMethod hashed(@Nullable MessageDigest md) {
if (md == null) {
md = getMessageDigest();
}
byte[] classDigest = md.digest(internalClassName.getBytes(CharsetToolkit.UTF8_CHARSET));
md.update(methodName.getBytes(CharsetToolkit.UTF8_CHARSET));
md.update(methodDesc.getBytes(CharsetToolkit.UTF8_CHARSET));
byte[] sigDigest = md.digest();
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 HMethod(digest);
}
@Override
public String toString() {
return internalClassName + ' ' + methodName + ' ' + methodDesc;

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2000-2017 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 org.jetbrains.annotations.Nullable;
import java.security.MessageDigest;
/**
* An uniquely method (class name+method name+signature) identifier: either {@link Method} or {@link HMethod}.
*/
public interface MethodDescriptor {
/**
* Creates and returns the hashed representation of this method descriptor.
* May return itself if already hashed. Note that hashed descriptor is not equal to
* non-hashed one.
*
* @param md message digest to use for hashing (could be null to use the default one)
* @return a corresponding HMethod.
*/
@NotNull HMethod hashed(@Nullable MessageDigest md);
}

View File

@@ -149,13 +149,13 @@ class NullableMethodAnalysis {
Calls calls = ((Calls)result);
int mergedMappedLabels = calls.mergedLabels;
if (mergedMappedLabels != 0) {
Set<Product> sum = new HashSet<>();
Key[] createdKeys = interpreter.keys;
Set<Component> sum = new HashSet<>();
EKey[] createdKeys = interpreter.keys;
for (int origin = 0; origin < originsMapping.length; origin++) {
int mappedOrigin = originsMapping[origin];
Key createdKey = createdKeys[origin];
EKey createdKey = createdKeys[origin];
if (createdKey != null && (mergedMappedLabels & (1 << mappedOrigin)) != 0) {
sum.add(new Product(Value.Null, Collections.singleton(createdKey)));
sum.add(new Component(Value.Null, Collections.singleton(createdKey)));
}
}
if (!sum.isEmpty()) {
@@ -211,7 +211,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
final InsnList insns;
final boolean[] origins;
private final int[] originsMapping;
final Key[] keys;
final EKey[] keys;
Constraint constraint;
int delta;
@@ -224,7 +224,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
this.insns = insns;
this.origins = origins;
this.originsMapping = originsMapping;
keys = new Key[originsMapping.length];
keys = new EKey[originsMapping.length];
}
@Override
@@ -344,7 +344,7 @@ class NullableMethodInterpreter extends BasicInterpreter implements InterpreterE
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
int label = 1 << originsMapping[insnIndex];
if (keys[insnIndex] == null) {
keys[insnIndex] = new Key(method, Direction.NullableOut, stable);
keys[insnIndex] = new EKey(method, Direction.NullableOut, stable);
}
return new Calls(label);
}

View File

@@ -38,18 +38,18 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*;
abstract class PResults {
// SoP = sum of products
static Set<Set<Key>> join(Set<Set<Key>> sop1, Set<Set<Key>> sop2) {
Set<Set<Key>> sop = new HashSet<>();
static Set<Set<EKey>> join(Set<Set<EKey>> sop1, Set<Set<EKey>> sop2) {
Set<Set<EKey>> sop = new HashSet<>();
sop.addAll(sop1);
sop.addAll(sop2);
return sop;
}
static Set<Set<Key>> meet(Set<Set<Key>> sop1, Set<Set<Key>> sop2) {
Set<Set<Key>> sop = new HashSet<>();
for (Set<Key> prod1 : sop1) {
for (Set<Key> prod2 : sop2) {
Set<Key> prod = new HashSet<>();
static Set<Set<EKey>> meet(Set<Set<EKey>> sop1, Set<Set<EKey>> sop2) {
Set<Set<EKey>> sop = new HashSet<>();
for (Set<EKey> prod1 : sop1) {
for (Set<EKey> prod2 : sop2) {
Set<EKey> prod = new HashSet<>();
prod.addAll(prod1);
prod.addAll(prod2);
sop.add(prod);
@@ -83,23 +83,23 @@ abstract class PResults {
}
};
static final class ConditionalNPE implements PResult {
final Set<Set<Key>> sop;
public ConditionalNPE(Set<Set<Key>> sop) throws AnalyzerException {
final Set<Set<EKey>> sop;
public ConditionalNPE(Set<Set<EKey>> sop) throws AnalyzerException {
this.sop = sop;
checkLimit(sop);
}
public ConditionalNPE(Key key) {
public ConditionalNPE(EKey key) {
sop = new HashSet<>();
Set<Key> prod = new HashSet<>();
Set<EKey> prod = new HashSet<>();
prod.add(key);
sop.add(prod);
}
static void checkLimit(Set<Set<Key>> sop) throws AnalyzerException {
static void checkLimit(Set<Set<EKey>> sop) throws AnalyzerException {
int size = sop.stream().mapToInt(Set::size).sum();
if (size > Analysis.EQUATION_SIZE_LIMIT) {
throw new AnalyzerException(null, "Equation size is too big");
throw new AnalyzerException(null, "HEquation size is too big");
}
}
}
@@ -198,7 +198,7 @@ class NonNullInAnalysis extends Analysis<PResult> {
}
else {
ConditionalNPE condNpe = (ConditionalNPE) result;
Set<Product> components = condNpe.sop.stream().map(prod -> new Product(Value.Top, prod)).collect(Collectors.toSet());
Set<Component> components = condNpe.sop.stream().map(prod -> new Component(Value.Top, prod)).collect(Collectors.toSet());
return new Equation(aKey, new Pending(components));
}
}
@@ -421,7 +421,7 @@ class NullableInAnalysis extends Analysis<PResult> {
}
else {
ConditionalNPE condNpe = (ConditionalNPE) result;
Set<Product> components = condNpe.sop.stream().map(prod -> new Product(Value.Top, prod)).collect(Collectors.toSet());
Set<Component> components = condNpe.sop.stream().map(prod -> new Component(Value.Top, prod)).collect(Collectors.toSet());
return new Equation(aKey, new Pending(components));
}
}
@@ -593,13 +593,13 @@ class NullableInAnalysis extends Analysis<PResult> {
abstract class NullityInterpreter extends BasicInterpreter {
boolean top;
final boolean nullableAnalysis;
final int nullityMask;
final boolean nullable;
private PResult subResult = Identity;
protected boolean taken;
NullityInterpreter(boolean nullableAnalysis, int nullityMask) {
NullityInterpreter(boolean nullableAnalysis, boolean nullable) {
this.nullableAnalysis = nullableAnalysis;
this.nullityMask = nullityMask;
this.nullable = nullable;
}
abstract PResult combine(PResult res1, PResult res2) throws AnalyzerException;
@@ -736,8 +736,8 @@ abstract class NullityInterpreter extends BasicInterpreter {
boolean stable = opcode == INVOKESTATIC || opcode == INVOKESPECIAL;
for (int i = 0; i < values.size(); i++) {
BasicValue value = values.get(i);
if (value instanceof ParamValue || (NullValue == value && nullityMask == In.NULLABLE_MASK && "<init>".equals(method.methodName))) {
subResult = combine(subResult, new ConditionalNPE(new Key(method, new In(i, nullityMask), stable)));
if (value instanceof ParamValue || (NullValue == value && nullable && "<init>".equals(method.methodName))) {
subResult = combine(subResult, new ConditionalNPE(new EKey(method, new In(i, nullable), stable)));
}
}
}
@@ -747,7 +747,7 @@ abstract class NullityInterpreter extends BasicInterpreter {
class NotNullInterpreter extends NullityInterpreter {
NotNullInterpreter() {
super(false, In.NOT_NULL_MASK);
super(false, false);
}
@Override
@@ -759,7 +759,7 @@ class NotNullInterpreter extends NullityInterpreter {
class NullableInterpreter extends NullityInterpreter {
NullableInterpreter() {
super(true, In.NULLABLE_MASK);
super(true, true);
}
@Override

View File

@@ -57,7 +57,7 @@ public class ProjectBytecodeAnalysis {
private final Project myProject;
private final boolean nullableMethod;
private final boolean nullableMethodTransitivity;
private final Map<Bytes, List<HEquations>> myEquationCache = ContainerUtil.createConcurrentSoftValueMap();
private final Map<HMethod, List<Equations>> myEquationCache = ContainerUtil.createConcurrentSoftValueMap();
public static ProjectBytecodeAnalysis getInstance(@NotNull Project project) {
return ServiceManager.getService(project, ProjectBytecodeAnalysis.class);
@@ -117,12 +117,12 @@ public class ProjectBytecodeAnalysis {
try {
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
HKey primaryKey = getKey(listOwner, md);
EKey primaryKey = getKey(listOwner, md);
if (primaryKey == null) {
return PsiAnnotation.EMPTY_ARRAY;
}
if (listOwner instanceof PsiMethod) {
ArrayList<HKey> allKeys = collectMethodKeys((PsiMethod)listOwner, primaryKey);
ArrayList<EKey> allKeys = collectMethodKeys((PsiMethod)listOwner, primaryKey);
MethodAnnotations methodAnnotations = loadMethodAnnotations((PsiMethod)listOwner, primaryKey, allKeys);
return toPsi(primaryKey, methodAnnotations);
} else if (listOwner instanceof PsiParameter) {
@@ -148,7 +148,7 @@ public class ProjectBytecodeAnalysis {
* @return Psi annotations
*/
@NotNull
private PsiAnnotation[] toPsi(HKey primaryKey, MethodAnnotations methodAnnotations) {
private PsiAnnotation[] toPsi(EKey primaryKey, MethodAnnotations methodAnnotations) {
boolean notNull = methodAnnotations.notNulls.contains(primaryKey);
boolean nullable = methodAnnotations.nullables.contains(primaryKey);
boolean pure = methodAnnotations.pures.contains(primaryKey);
@@ -233,7 +233,7 @@ public class ProjectBytecodeAnalysis {
}
@Nullable
public static HKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) {
public static EKey getKey(@NotNull PsiModifierListOwner owner, MessageDigest md) {
LOG.assertTrue(owner instanceof PsiCompiledElement, owner);
if (owner instanceof PsiMethod) {
return BytecodeAnalysisConverter.psiKey((PsiMethod)owner, Out, md);
@@ -244,7 +244,7 @@ public class ProjectBytecodeAnalysis {
PsiElement gParent = parent.getParent();
if (gParent instanceof PsiMethod) {
final int index = ((PsiParameterList)parent).getParameterIndex((PsiParameter)owner);
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, In.NOT_NULL_MASK), md);
return BytecodeAnalysisConverter.psiKey((PsiMethod)gParent, new In(index, false), md);
}
}
}
@@ -258,44 +258,44 @@ public class ProjectBytecodeAnalysis {
* @param primaryKey primary compressed key for this method
* @return compressed keys for this method
*/
public static ArrayList<HKey> collectMethodKeys(@NotNull PsiMethod method, HKey primaryKey) {
public static ArrayList<EKey> collectMethodKeys(@NotNull PsiMethod method, EKey primaryKey) {
return BytecodeAnalysisConverter.mkInOutKeys(method, primaryKey);
}
private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey)
private ParameterAnnotations loadParameterAnnotations(@NotNull EKey notNullKey)
throws EquationsLimitException {
final Solver notNullSolver = new Solver(new ELattice<>(Value.NotNull, Value.Top), Value.Top);
collectEquations(Collections.singletonList(notNullKey), notNullSolver);
Map<HKey, Value> notNullSolutions = notNullSolver.solve();
Map<EKey, Value> notNullSolutions = notNullSolver.solve();
// subtle point
boolean notNull =
(Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable()));
final Solver nullableSolver = new Solver(new ELattice<>(Value.Null, Value.Top), Value.Top);
final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true, false);
final EKey nullableKey = new EKey(notNullKey.method, notNullKey.dirKey + 1, true, false);
collectEquations(Collections.singletonList(nullableKey), nullableSolver);
Map<HKey, Value> nullableSolutions = nullableSolver.solve();
Map<EKey, Value> nullableSolutions = nullableSolver.solve();
// subtle point
boolean nullable =
(Value.Null == nullableSolutions.get(nullableKey)) || (Value.Null == nullableSolutions.get(nullableKey.mkUnstable()));
return new ParameterAnnotations(notNull, nullable);
}
private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull HKey key, ArrayList<HKey> allKeys)
private MethodAnnotations loadMethodAnnotations(@NotNull PsiMethod owner, @NotNull EKey key, ArrayList<EKey> allKeys)
throws EquationsLimitException {
MethodAnnotations result = new MethodAnnotations();
final PuritySolver puritySolver = new PuritySolver();
collectPurityEquations(key.withDirection(Pure), puritySolver);
Map<HKey, Set<HEffectQuantum>> puritySolutions = puritySolver.solve();
Map<EKey, Set<EffectQuantum>> puritySolutions = puritySolver.solve();
int arity = owner.getParameterList().getParameters().length;
BytecodeAnalysisConverter.addEffectAnnotations(puritySolutions, result, key, owner.isConstructor());
HKey failureKey = key.withDirection(Throw);
EKey failureKey = key.withDirection(Throw);
final Solver failureSolver = new Solver(new ELattice<>(Value.Fail, Value.Top), Value.Top);
collectEquations(Collections.singletonList(failureKey), failureSolver);
if (failureSolver.solve().get(failureKey) == Value.Fail) {
@@ -304,20 +304,20 @@ public class ProjectBytecodeAnalysis {
} else {
final Solver outSolver = new Solver(new ELattice<>(Value.Bot, Value.Top), Value.Top);
collectEquations(allKeys, outSolver);
Map<HKey, Value> solutions = outSolver.solve();
Map<EKey, Value> solutions = outSolver.solve();
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
}
if (nullableMethod) {
final Solver nullableMethodSolver = new Solver(new ELattice<>(Value.Bot, Value.Null), Value.Bot);
HKey nullableKey = key.withDirection(NullableOut);
EKey nullableKey = key.withDirection(NullableOut);
if (nullableMethodTransitivity) {
collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver);
}
else {
collectSingleEquation(nullableKey, nullableMethodSolver);
}
Map<HKey, Value> nullableSolutions = nullableMethodSolver.solve();
Map<EKey, Value> nullableSolutions = nullableMethodSolver.solve();
if (nullableSolutions.get(nullableKey) == Value.Null || nullableSolutions.get(nullableKey.invertStability()) == Value.Null) {
result.nullables.add(key);
}
@@ -325,18 +325,19 @@ public class ProjectBytecodeAnalysis {
return result;
}
private List<HEquations> getEquations(Bytes key) {
List<HEquations> result = myEquationCache.get(key);
private List<Equations> getEquations(MethodDescriptor methodDescriptor) {
HMethod key = methodDescriptor.hashed(null);
List<Equations> result = myEquationCache.get(key);
if (result == null) {
myEquationCache.put(key, result = BytecodeAnalysisIndex.getEquations(ProjectScope.getLibrariesScope(myProject), key));
}
return result;
}
private void collectPurityEquations(HKey key, PuritySolver puritySolver)
private void collectPurityEquations(EKey key, PuritySolver puritySolver)
throws EquationsLimitException {
HashSet<HKey> queued = new HashSet<>();
Stack<HKey> queue = new Stack<>();
HashSet<EKey> queued = new HashSet<>();
Stack<EKey> queue = new Stack<>();
queue.push(key);
queued.add(key);
@@ -346,19 +347,18 @@ public class ProjectBytecodeAnalysis {
throw new EquationsLimitException();
}
ProgressManager.checkCanceled();
HKey hKey = queue.pop();
Bytes bytes = new Bytes(hKey.key);
EKey hKey = queue.pop();
for (HEquations hEquations : getEquations(bytes)) {
boolean stable = hEquations.stable;
for (DirectionResultPair pair : hEquations.results) {
for (Equations equations : getEquations(hKey.method)) {
boolean stable = equations.stable;
for (DirectionResultPair pair : equations.results) {
int dirKey = pair.directionKey;
if (dirKey == hKey.dirKey) {
Set<HEffectQuantum> effects = ((HEffects)pair.hResult).effects;
puritySolver.addEquation(new HKey(bytes.bytes, dirKey, stable, false), effects);
for (HEffectQuantum effect : effects) {
if (effect instanceof HEffectQuantum.CallQuantum) {
HKey depKey = ((HEffectQuantum.CallQuantum)effect).key;
Set<EffectQuantum> effects = ((Effects)pair.hResult).effects;
puritySolver.addEquation(new EKey(hKey.method, dirKey, stable, false), effects);
for (EffectQuantum effect : effects) {
if (effect instanceof EffectQuantum.CallQuantum) {
EKey depKey = ((EffectQuantum.CallQuantum)effect).key;
if (!queued.contains(depKey)) {
queue.push(depKey);
queued.add(depKey);
@@ -371,11 +371,11 @@ public class ProjectBytecodeAnalysis {
}
}
private void collectEquations(List<HKey> keys, Solver solver) throws EquationsLimitException {
HashSet<HKey> queued = new HashSet<>();
Stack<HKey> queue = new Stack<>();
private void collectEquations(List<EKey> keys, Solver solver) throws EquationsLimitException {
HashSet<EKey> queued = new HashSet<>();
Stack<EKey> queue = new Stack<>();
for (HKey key : keys) {
for (EKey key : keys) {
queue.push(key);
queued.add(key);
}
@@ -385,21 +385,20 @@ public class ProjectBytecodeAnalysis {
throw new EquationsLimitException();
}
ProgressManager.checkCanceled();
HKey hKey = queue.pop();
Bytes bytes = new Bytes(hKey.key);
EKey hKey = queue.pop();
for (HEquations hEquations : getEquations(bytes)) {
boolean stable = hEquations.stable;
for (DirectionResultPair pair : hEquations.results) {
for (Equations equations : getEquations(hKey.method)) {
boolean stable = equations.stable;
for (DirectionResultPair pair : equations.results) {
int dirKey = pair.directionKey;
if (dirKey == hKey.dirKey) {
HResult result = pair.hResult;
Result result = pair.hResult;
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result));
if (result instanceof HPending) {
HPending pending = (HPending)result;
for (HComponent component : pending.delta) {
for (HKey depKey : component.ids) {
solver.addEquation(new Equation(new EKey(hKey.method, dirKey, stable, false), result));
if (result instanceof Pending) {
Pending pending = (Pending)result;
for (Component component : pending.delta) {
for (EKey depKey : component.ids) {
if (!queued.contains(depKey)) {
queue.push(depKey);
queued.add(depKey);
@@ -413,17 +412,16 @@ public class ProjectBytecodeAnalysis {
}
}
private void collectSingleEquation(HKey hKey, Solver solver) throws EquationsLimitException {
private void collectSingleEquation(EKey hKey, Solver solver) throws EquationsLimitException {
ProgressManager.checkCanceled();
Bytes bytes = new Bytes(hKey.key);
for (HEquations hEquations : getEquations(bytes)) {
boolean stable = hEquations.stable;
for (DirectionResultPair pair : hEquations.results) {
for (Equations equations : getEquations(hKey.method)) {
boolean stable = equations.stable;
for (DirectionResultPair pair : equations.results) {
int dirKey = pair.directionKey;
if (dirKey == hKey.dirKey) {
HResult result = pair.hResult;
solver.addEquation(new HEquation(new HKey(bytes.bytes, dirKey, stable, false), result));
Result result = pair.hResult;
solver.addEquation(new Equation(new EKey(hKey.method, dirKey, stable, false), result));
}
}
}
@@ -440,13 +438,13 @@ public class ProjectBytecodeAnalysis {
class MethodAnnotations {
// @NotNull keys
final Set<HKey> notNulls = new HashSet<>(1);
final Set<EKey> notNulls = new HashSet<>(1);
// @Nullable keys
final Set<HKey> nullables = new HashSet<>(1);
final Set<EKey> nullables = new HashSet<>(1);
// @Contract(pure=true) part of contract
final Set<HKey> pures = new HashSet<>(1);
final Set<EKey> pures = new HashSet<>(1);
// @Contracts
final Map<HKey, String> contractsValues = new HashMap<>();
final Map<EKey, String> contractsValues = new HashMap<>();
}
class ParameterAnnotations {

View File

@@ -36,14 +36,14 @@ import java.util.*;
*/
public class PurityAnalysis {
static final Set<EffectQuantum> topEffect = Collections.singleton(EffectQuantum.TopEffectQuantum);
static final Set<HEffectQuantum> topHEffect = Collections.singleton(HEffectQuantum.TopEffectQuantum);
static final Set<EffectQuantum> topHEffect = Collections.singleton(EffectQuantum.TopEffectQuantum);
static final int UN_ANALYZABLE_FLAG = Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE | Opcodes.ACC_INTERFACE;
@NotNull
public static Equation analyze(Method method, MethodNode methodNode, boolean stable) {
Key key = new Key(method, Direction.Pure, stable);
Set<EffectQuantum> hardCodedSolution = HardCodedPurity.getHardCodedSolution(key);
EKey key = new EKey(method, Direction.Pure, stable);
Set<EffectQuantum> hardCodedSolution = HardCodedPurity.getHardCodedSolution(method);
if (hardCodedSolution != null) {
return new Equation(key, new Effects(hardCodedSolution));
}
@@ -170,81 +170,10 @@ abstract class DataValue implements org.jetbrains.org.objectweb.asm.tree.analysi
};
}
interface EffectQuantum {
EffectQuantum TopEffectQuantum = new EffectQuantum() {
@Override
public String toString() {
return "Top";
}
};
EffectQuantum ThisChangeQuantum = new EffectQuantum() {
@Override
public String toString() {
return "Changes this";
}
};
final class ParamChangeQuantum implements EffectQuantum {
final int n;
public ParamChangeQuantum(int n) {
this.n = n;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ParamChangeQuantum)) return false;
return n == ((ParamChangeQuantum)o).n;
}
@Override
public int hashCode() {
return n;
}
@Override
public String toString() {
return "Changes param#" + n;
}
}
final class CallQuantum implements EffectQuantum {
final @NotNull Key key;
final @NotNull DataValue[] data;
final boolean isStatic;
public CallQuantum(@NotNull Key key, @NotNull DataValue[] data, boolean isStatic) {
this.key = key;
this.data = data;
this.isStatic = isStatic;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof CallQuantum)) return false;
CallQuantum quantum = (CallQuantum)o;
return isStatic == quantum.isStatic && key.equals(quantum.key) && Arrays.equals(data, quantum.data);
}
@Override
public int hashCode() {
return 31 * (31 * key.hashCode() + Arrays.hashCode(data)) + (isStatic ? 1 : 0);
}
@Override
public String toString() {
return "Calls " + key;
}
}
}
abstract class HEffectQuantum {
abstract class EffectQuantum {
private final int myHash;
HEffectQuantum(int hash) {
EffectQuantum(int hash) {
myHash = hash;
}
@@ -253,19 +182,19 @@ abstract class HEffectQuantum {
return myHash;
}
static final HEffectQuantum TopEffectQuantum = new HEffectQuantum(-1) {
static final EffectQuantum TopEffectQuantum = new EffectQuantum(-1) {
@Override
public String toString() {
return "Top";
}
};
static final HEffectQuantum ThisChangeQuantum = new HEffectQuantum(-2) {
static final EffectQuantum ThisChangeQuantum = new EffectQuantum(-2) {
@Override
public String toString() {
return "Changes this";
}
};
static class ParamChangeQuantum extends HEffectQuantum {
static class ParamChangeQuantum extends EffectQuantum {
final int n;
public ParamChangeQuantum(int n) {
super(n);
@@ -289,11 +218,11 @@ abstract class HEffectQuantum {
return "Changes param#" + n;
}
}
static class CallQuantum extends HEffectQuantum {
final HKey key;
static class CallQuantum extends EffectQuantum {
final EKey key;
final DataValue[] data;
final boolean isStatic;
public CallQuantum(HKey key, DataValue[] data, boolean isStatic) {
public CallQuantum(EKey key, DataValue[] data, boolean isStatic) {
super((key.hashCode() * 31 + Arrays.hashCode(data)) * 31 + (isStatic ? 1 : 0));
this.key = key;
this.data = data;
@@ -453,14 +382,15 @@ class DataInterpreter extends Interpreter<DataValue> {
boolean stable = opCode == Opcodes.INVOKESPECIAL || opCode == Opcodes.INVOKESTATIC;
MethodInsnNode mNode = ((MethodInsnNode)insn);
DataValue[] data = values.toArray(new DataValue[0]);
Key key = new Key(new Method(mNode.owner, mNode.name, mNode.desc), Direction.Pure, stable);
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
EKey key = new EKey(method, Direction.Pure, stable);
EffectQuantum quantum = new EffectQuantum.CallQuantum(key, data, opCode == Opcodes.INVOKESTATIC);
DataValue result = (ASMUtils.getReturnSizeFast(mNode.desc) == 1) ? DataValue.UnknownDataValue1 : DataValue.UnknownDataValue2;
if (HardCodedPurity.isPureMethod(key)) {
if (HardCodedPurity.isPureMethod(method)) {
quantum = null;
result = DataValue.LocalDataValue;
}
else if (HardCodedPurity.isThisChangingMethod(key)) {
else if (HardCodedPurity.isThisChangingMethod(method)) {
DataValue receiver = ArrayUtil.getFirstElement(data);
if (receiver == DataValue.ThisDataValue) {
quantum = EffectQuantum.ThisChangeQuantum;
@@ -468,7 +398,7 @@ class DataInterpreter extends Interpreter<DataValue> {
else if (receiver == DataValue.LocalDataValue || receiver == DataValue.OwnedDataValue) {
quantum = null;
}
if (HardCodedPurity.isBuilderChainCall(key)) {
if (HardCodedPurity.isBuilderChainCall(method)) {
// mostly to support string concatenation
result = receiver;
}
@@ -575,16 +505,15 @@ final class HardCodedPurity {
solutions.put(new Method("java/lang/Object", "hashCode", "()I"), Collections.emptySet());
}
static Set<EffectQuantum> getHardCodedSolution(Key key) {
return isThisChangingMethod(key) ? thisChange : isPureMethod(key) ? Collections.emptySet() : solutions.get(key.method);
static Set<EffectQuantum> getHardCodedSolution(Method method) {
return isThisChangingMethod(method) ? thisChange : isPureMethod(method) ? Collections.emptySet() : solutions.get(method);
}
static boolean isThisChangingMethod(Key key) {
return isBuilderChainCall(key) || thisChangingMethods.contains(key.method);
static boolean isThisChangingMethod(Method method) {
return isBuilderChainCall(method) || thisChangingMethods.contains(method);
}
static boolean isBuilderChainCall(Key key) {
Method method = key.method;
static boolean isBuilderChainCall(Method method) {
// Those methods are virtual, thus contracts cannot be inferred automatically,
// but all possible implementations are controlled
// (only final classes j.l.StringBuilder and j.l.StringBuffer extend package-private j.l.AbstractStringBuilder)
@@ -592,9 +521,9 @@ final class HardCodedPurity {
method.methodName.startsWith("append");
}
static boolean isPureMethod(Key key) {
return key.method.methodName.equals("toString") && key.method.methodDesc.equals("()Ljava/lang/String;") ||
pureMethods.contains(key.method);
static boolean isPureMethod(Method method) {
return method.methodName.equals("toString") && method.methodDesc.equals("()Ljava/lang/String;") ||
pureMethods.contains(method);
}
static boolean isOwnedField(FieldInsnNode fieldInsn) {
@@ -603,16 +532,16 @@ final class HardCodedPurity {
}
final class PuritySolver {
private HashMap<HKey, Set<HEffectQuantum>> solved = new HashMap<>();
private HashMap<HKey, Set<HKey>> dependencies = new HashMap<>();
private final Stack<HKey> moving = new Stack<>();
private HashMap<HKey, Set<HEffectQuantum>> pending = new HashMap<>();
private HashMap<EKey, Set<EffectQuantum>> solved = new HashMap<>();
private HashMap<EKey, Set<EKey>> dependencies = new HashMap<>();
private final Stack<EKey> moving = new Stack<>();
private HashMap<EKey, Set<EffectQuantum>> pending = new HashMap<>();
void addEquation(HKey key, Set<HEffectQuantum> effects) {
Set<HKey> callKeys = new HashSet<>();
for (HEffectQuantum effect : effects) {
if (effect instanceof HEffectQuantum.CallQuantum) {
callKeys.add(((HEffectQuantum.CallQuantum)effect).key);
void addEquation(EKey key, Set<EffectQuantum> effects) {
Set<EKey> callKeys = new HashSet<>();
for (EffectQuantum effect : effects) {
if (effect instanceof EffectQuantum.CallQuantum) {
callKeys.add(((EffectQuantum.CallQuantum)effect).key);
}
}
@@ -621,8 +550,8 @@ final class PuritySolver {
moving.add(key);
} else {
pending.put(key, effects);
for (HKey callKey : callKeys) {
Set<HKey> deps = dependencies.get(callKey);
for (EKey callKey : callKeys) {
Set<EKey> deps = dependencies.get(callKey);
if (deps == null) {
deps = new HashSet<>();
dependencies.put(callKey, deps);
@@ -632,41 +561,41 @@ final class PuritySolver {
}
}
public Map<HKey, Set<HEffectQuantum>> solve() {
public Map<EKey, Set<EffectQuantum>> solve() {
while (!moving.isEmpty()) {
HKey key = moving.pop();
Set<HEffectQuantum> effects = solved.get(key);
EKey key = moving.pop();
Set<EffectQuantum> effects = solved.get(key);
HKey[] propagateKeys;
EKey[] propagateKeys;
Set[] propagateEffects;
if (key.stable) {
propagateKeys = new HKey[]{key, key.mkUnstable()};
propagateKeys = new EKey[]{key, key.mkUnstable()};
propagateEffects = new Set[]{effects, effects};
}
else {
propagateKeys = new HKey[]{key.mkStable(), key};
propagateKeys = new EKey[]{key.mkStable(), key};
propagateEffects = new Set[]{effects, PurityAnalysis.topHEffect};
}
for (int i = 0; i < propagateKeys.length; i++) {
HKey pKey = propagateKeys[i];
EKey pKey = propagateKeys[i];
@SuppressWarnings("unchecked")
Set<HEffectQuantum> pEffects = propagateEffects[i];
Set<HKey> dKeys = dependencies.remove(pKey);
Set<EffectQuantum> pEffects = propagateEffects[i];
Set<EKey> dKeys = dependencies.remove(pKey);
if (dKeys != null) {
for (HKey dKey : dKeys) {
Set<HEffectQuantum> dEffects = pending.remove(dKey);
for (EKey dKey : dKeys) {
Set<EffectQuantum> dEffects = pending.remove(dKey);
if (dEffects == null) {
// already solved, for example, solution is top
continue;
}
Set<HKey> callKeys = new HashSet<>();
Set<HEffectQuantum> newEffects = new HashSet<>();
Set<HEffectQuantum> delta = null;
Set<EKey> callKeys = new HashSet<>();
Set<EffectQuantum> newEffects = new HashSet<>();
Set<EffectQuantum> delta = null;
for (HEffectQuantum dEffect : dEffects) {
if (dEffect instanceof HEffectQuantum.CallQuantum) {
HEffectQuantum.CallQuantum call = ((HEffectQuantum.CallQuantum)dEffect);
for (EffectQuantum dEffect : dEffects) {
if (dEffect instanceof EffectQuantum.CallQuantum) {
EffectQuantum.CallQuantum call = ((EffectQuantum.CallQuantum)dEffect);
if (call.key.equals(pKey)) {
delta = substitute(pEffects, call.data, call.isStatic);
newEffects.addAll(delta);
@@ -702,29 +631,29 @@ final class PuritySolver {
return solved;
}
private static Set<HEffectQuantum> substitute(Set<HEffectQuantum> effects, DataValue[] data, boolean isStatic) {
private static Set<EffectQuantum> substitute(Set<EffectQuantum> effects, DataValue[] data, boolean isStatic) {
if (effects.isEmpty() || PurityAnalysis.topHEffect.equals(effects)) {
return effects;
}
Set<HEffectQuantum> newEffects = new HashSet<>(effects.size());
Set<EffectQuantum> newEffects = new HashSet<>(effects.size());
int shift = isStatic ? 0 : 1;
for (HEffectQuantum effect : effects) {
for (EffectQuantum effect : effects) {
DataValue arg = null;
if (effect == HEffectQuantum.ThisChangeQuantum) {
if (effect == EffectQuantum.ThisChangeQuantum) {
arg = data[0];
} else if (effect instanceof HEffectQuantum.ParamChangeQuantum) {
HEffectQuantum.ParamChangeQuantum paramChange = ((HEffectQuantum.ParamChangeQuantum)effect);
} else if (effect instanceof EffectQuantum.ParamChangeQuantum) {
EffectQuantum.ParamChangeQuantum paramChange = ((EffectQuantum.ParamChangeQuantum)effect);
arg = data[paramChange.n + shift];
}
if (arg == null || arg == DataValue.LocalDataValue) {
continue;
}
if (arg == DataValue.ThisDataValue || arg == DataValue.OwnedDataValue) {
newEffects.add(HEffectQuantum.ThisChangeQuantum);
newEffects.add(EffectQuantum.ThisChangeQuantum);
continue;
}
if (arg instanceof DataValue.ParameterDataValue) {
newEffects.add(new HEffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)arg).n));
newEffects.add(new EffectQuantum.ParamChangeQuantum(((DataValue.ParameterDataValue)arg).n));
continue;
}
return PurityAnalysis.topHEffect;

View File

@@ -17,7 +17,6 @@ package com.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import java.util.*;
@@ -50,6 +49,7 @@ final class ELattice<T extends Enum<T>> {
class ResultUtil {
private static final EKey[] EMPTY_PRODUCT = new EKey[0];
private final ELattice<Value> lattice;
final Value top;
ResultUtil(ELattice<Value> lattice) {
@@ -57,7 +57,7 @@ class ResultUtil {
top = lattice.top;
}
Result join(Result r1, Result r2) throws AnalyzerException {
Result join(Result r1, Result r2) {
if (r1 instanceof Final && ((Final) r1).value == top) {
return r1;
}
@@ -68,160 +68,37 @@ class ResultUtil {
return new Final(lattice.join(((Final) r1).value, ((Final) r2).value));
}
if (r1 instanceof Final && r2 instanceof Pending) {
Final f1 = (Final)r1;
Pending pending = (Pending) r2;
Set<Product> sum1 = new HashSet<>(pending.sum);
sum1.add(new Product(f1.value, Collections.emptySet()));
return new Pending(sum1);
return addSingle((Pending)r2, (Final)r1);
}
if (r1 instanceof Pending && r2 instanceof Final) {
Final f2 = (Final)r2;
Pending pending = (Pending) r1;
Set<Product> sum1 = new HashSet<>(pending.sum);
sum1.add(new Product(f2.value, Collections.emptySet()));
return new Pending(sum1);
return addSingle((Pending)r1, (Final)r2);
}
assert r1 instanceof Pending && r2 instanceof Pending;
Pending pending1 = (Pending) r1;
Pending pending2 = (Pending) r2;
Set<Product> sum = new HashSet<>();
sum.addAll(pending1.sum);
sum.addAll(pending2.sum);
checkLimit(sum);
Set<Component> sum = new HashSet<>();
sum.addAll(Arrays.asList(pending1.delta));
sum.addAll(Arrays.asList(pending2.delta));
return new Pending(sum);
}
private static void checkLimit(Set<Product> sum) throws AnalyzerException {
int size = sum.stream().mapToInt(prod -> prod.ids.size()).sum();
if (size > Analysis.EQUATION_SIZE_LIMIT) {
throw new AnalyzerException(null, "Equation size is too big");
@NotNull
private static Pending addSingle(Pending pending, Final result) {
Component component = new Component(result.value, EMPTY_PRODUCT);
if(ArrayUtil.contains(component, pending.delta)) {
return pending;
}
}
}
class HResultUtil {
private static final HKey[] EMPTY_PRODUCT = new HKey[0];
private final ELattice<Value> lattice;
final Value top;
HResultUtil(ELattice<Value> lattice) {
this.lattice = lattice;
top = lattice.top;
}
HResult join(HResult r1, HResult r2) {
if (r1 instanceof HFinal && ((HFinal) r1).value == top) {
return r1;
}
if (r2 instanceof HFinal && ((HFinal) r2).value == top) {
return r2;
}
if (r1 instanceof HFinal && r2 instanceof HFinal) {
return new HFinal(lattice.join(((HFinal) r1).value, ((HFinal) r2).value));
}
if (r1 instanceof HFinal && r2 instanceof HPending) {
HFinal f1 = (HFinal)r1;
HPending pending = (HPending) r2;
HComponent[] delta = new HComponent[pending.delta.length + 1];
delta[0] = new HComponent(f1.value, EMPTY_PRODUCT);
System.arraycopy(pending.delta, 0, delta, 1, pending.delta.length);
return new HPending(delta);
}
if (r1 instanceof HPending && r2 instanceof HFinal) {
HFinal f2 = (HFinal)r2;
HPending pending = (HPending) r1;
HComponent[] delta = new HComponent[pending.delta.length + 1];
delta[0] = new HComponent(f2.value, EMPTY_PRODUCT);
System.arraycopy(pending.delta, 0, delta, 1, pending.delta.length);
return new HPending(delta);
}
HPending pending1 = (HPending) r1;
HPending pending2 = (HPending) r2;
return new HPending(ArrayUtil.mergeArrays(pending1.delta, pending2.delta, HComponent.ARRAY_FACTORY));
}
}
final class Product {
@NotNull final Value value;
@NotNull final Set<Key> ids;
Product(@NotNull Value value, @NotNull Set<Key> 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;
Product product = (Product)o;
if (!ids.equals(product.ids)) return false;
if (!value.equals(product.value)) return false;
return true;
}
@Override
public int hashCode() {
int result = value.hashCode();
result = 31 * result + ids.hashCode();
return result;
}
}
interface Result {}
final class Final implements Result {
final Value value;
Final(Value value) {
this.value = value;
}
@Override
public String toString() {
return "Final{" + "value=" + value + '}';
}
}
final class Pending implements Result {
final Set<Product> sum;
Pending(Set<Product> sum) {
this.sum = sum;
}
}
final class Effects implements Result {
final Set<EffectQuantum> effects;
Effects(Set<EffectQuantum> effects) {
this.effects = effects;
}
}
final class Equation {
final Key id;
final Result rhs;
Equation(Key id, Result rhs) {
this.id = id;
this.rhs = rhs;
}
@Override
public String toString() {
return "Equation{" + "id=" + id + ", rhs=" + rhs + '}';
return new Pending(ArrayUtil.append(pending.delta, component));
}
}
final class CoreHKey {
@NotNull
final byte[] key;
final MethodDescriptor myMethod;
final int dirKey;
CoreHKey(@NotNull byte[] key, int dirKey) {
this.key = key;
CoreHKey(@NotNull MethodDescriptor method, int dirKey) {
this.myMethod = method;
this.dirKey = dirKey;
}
@@ -231,83 +108,78 @@ final class CoreHKey {
if (o == null || getClass() != o.getClass()) return false;
CoreHKey coreHKey = (CoreHKey)o;
if (dirKey != coreHKey.dirKey) return false;
if (!Arrays.equals(key, coreHKey.key)) return false;
return true;
return dirKey == coreHKey.dirKey && myMethod.equals(coreHKey.myMethod);
}
@Override
public int hashCode() {
int result = Arrays.hashCode(key);
result = 31 * result + dirKey;
return result;
return 31 * myMethod.hashCode() + dirKey;
}
@Override
public String toString() {
return "CoreHKey [" + HKey.bytesToString(key) + "|" + Direction.fromInt(dirKey) + "]";
return "CoreHKey [" + myMethod + "|" + Direction.fromInt(dirKey) + "]";
}
}
final class Solver {
private final ELattice<Value> lattice;
private final HashMap<HKey, HashSet<HKey>> dependencies = new HashMap<>();
private final HashMap<HKey, HPending> pending = new HashMap<>();
private final HashMap<HKey, Value> solved = new HashMap<>();
private final Stack<HKey> moving = new Stack<>();
private final HashMap<EKey, HashSet<EKey>> dependencies = new HashMap<>();
private final HashMap<EKey, Pending> pending = new HashMap<>();
private final HashMap<EKey, Value> solved = new HashMap<>();
private final Stack<EKey> moving = new Stack<>();
private final HResultUtil resultUtil;
private final HashMap<CoreHKey, HEquation> equations = new HashMap<>();
private final ResultUtil resultUtil;
private final HashMap<CoreHKey, Equation> equations = new HashMap<>();
private final Value unstableValue;
Solver(ELattice<Value> lattice, Value unstableValue) {
this.lattice = lattice;
this.unstableValue = unstableValue;
resultUtil = new HResultUtil(lattice);
resultUtil = new ResultUtil(lattice);
}
void addEquation(HEquation equation) {
HKey key = equation.key;
CoreHKey coreKey = new CoreHKey(key.key, key.dirKey);
void addEquation(Equation equation) {
EKey key = equation.key;
CoreHKey coreKey = new CoreHKey(key.method, key.dirKey);
HEquation previousEquation = equations.get(coreKey);
Equation previousEquation = equations.get(coreKey);
if (previousEquation == null) {
equations.put(coreKey, equation);
} else {
HKey joinKey = new HKey(coreKey.key, coreKey.dirKey, equation.key.stable && previousEquation.key.stable, true);
HResult joinResult = resultUtil.join(equation.result, previousEquation.result);
HEquation joinEquation = new HEquation(joinKey, joinResult);
EKey joinKey = new EKey(coreKey.myMethod, coreKey.dirKey, equation.key.stable && previousEquation.key.stable, true);
Result joinResult = resultUtil.join(equation.result, previousEquation.result);
Equation joinEquation = new Equation(joinKey, joinResult);
equations.put(coreKey, joinEquation);
}
}
void queueEquation(HEquation equation) {
HResult rhs = equation.result;
if (rhs instanceof HFinal) {
solved.put(equation.key, ((HFinal) rhs).value);
void queueEquation(Equation equation) {
Result rhs = equation.result;
if (rhs instanceof Final) {
solved.put(equation.key, ((Final) 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);
} else if (rhs instanceof Pending) {
Pending pendResult = ((Pending)rhs).copy();
Result norm = normalize(pendResult.delta);
if (norm instanceof Final) {
solved.put(equation.key, ((Final) norm).value);
moving.push(equation.key);
}
else {
HPending pendResult1 = ((HPending)rhs).copy();
for (HComponent component : pendResult1.delta) {
for (HKey trigger : component.ids) {
HashSet<HKey> set = dependencies.get(trigger);
Pending pendResult1 = ((Pending)rhs).copy();
for (Component component : pendResult1.delta) {
for (EKey trigger : component.ids) {
HashSet<EKey> set = dependencies.get(trigger);
if (set == null) {
set = new HashSet<>();
dependencies.put(trigger, set);
}
set.add(equation.key);
}
pending.put(equation.key, pendResult1);
}
pending.put(equation.key, pendResult1);
}
}
}
@@ -323,38 +195,38 @@ final class Solver {
}
}
Map<HKey, Value> solve() {
for (HEquation hEquation : equations.values()) {
queueEquation(hEquation);
Map<EKey, Value> solve() {
for (Equation equation : equations.values()) {
queueEquation(equation);
}
while (!moving.empty()) {
HKey id = moving.pop();
EKey id = moving.pop();
Value value = solved.get(id);
HKey[] initialPIds = id.stable ? new HKey[]{id, id.invertStability()} : new HKey[]{id.invertStability(), id};
EKey[] initialPIds = id.stable ? new EKey[]{id, id.invertStability()} : new EKey[]{id.invertStability(), id};
Value[] initialPVals = id.stable ? new Value[]{value, value} : new Value[]{value, unstableValue};
HKey[] pIds = new HKey[]{initialPIds[0], initialPIds[1], initialPIds[0].negate(), initialPIds[1].negate()};
EKey[] pIds = new EKey[]{initialPIds[0], initialPIds[1], initialPIds[0].negate(), initialPIds[1].negate()};
Value[] pVals = new Value[]{initialPVals[0], initialPVals[1], negate(initialPVals[0]), negate(initialPVals[1])};
for (int i = 0; i < pIds.length; i++) {
HKey pId = pIds[i];
EKey pId = pIds[i];
Value pVal = pVals[i];
HashSet<HKey> dIds = dependencies.get(pId);
HashSet<EKey> dIds = dependencies.get(pId);
if (dIds == null) {
continue;
}
for (HKey dId : dIds) {
HPending pend = pending.remove(dId);
for (EKey dId : dIds) {
Pending pend = pending.remove(dId);
if (pend != null) {
HResult pend1 = substitute(pend, pId, pVal);
if (pend1 instanceof HFinal) {
HFinal fi = (HFinal)pend1;
Result pend1 = substitute(pend, pId, pVal);
if (pend1 instanceof Final) {
Final fi = (Final)pend1;
solved.put(dId, fi.value);
moving.push(dId);
}
else {
pending.put(dId, (HPending)pend1);
pending.put(dId, (Pending)pend1);
}
}
}
@@ -365,9 +237,9 @@ final class Solver {
}
// substitute id -> value into pending
HResult substitute(@NotNull HPending pending, @NotNull HKey id, @NotNull Value value) {
HComponent[] sum = pending.delta;
for (HComponent intIdComponent : sum) {
Result substitute(@NotNull Pending pending, @NotNull EKey id, @NotNull Value value) {
Component[] sum = pending.delta;
for (Component intIdComponent : sum) {
if (intIdComponent.remove(id)) {
intIdComponent.value = lattice.meet(intIdComponent.value, value);
}
@@ -375,17 +247,17 @@ final class Solver {
return normalize(sum);
}
@NotNull HResult normalize(@NotNull HComponent[] sum) {
@NotNull Result normalize(@NotNull Component[] sum) {
Value acc = lattice.bot;
boolean computableNow = true;
for (HComponent prod : sum) {
for (Component prod : sum) {
if (prod.isEmpty() || prod.value == lattice.bot) {
acc = lattice.join(acc, prod.value);
} else {
computableNow = false;
}
}
return (acc == lattice.top || computableNow) ? new HFinal(acc) : new HPending(sum);
return (acc == lattice.top || computableNow) ? new Final(acc) : new Pending(sum);
}
}

View File

@@ -281,7 +281,6 @@
</item>
<item name='java.io.File java.lang.String slashify(java.lang.String, boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null,_-&gt;!null&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -343,11 +343,6 @@
<item name='java.lang.invoke.BoundMethodHandle java.lang.RuntimeException badBoundArgumentException(java.lang.Object, java.lang.invoke.MethodHandle, int) 1'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.lang.invoke.BoundMethodHandle java.lang.String noParens(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.lang.invoke.BoundMethodHandle java.lang.String noParens(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -486,11 +486,6 @@
<val val="&quot;!null-&gt;!null;null-&gt;null&quot;"/>
</annotation>
</item>
<item name='java.net.URI java.lang.String encode(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.net.URI java.lang.String encode(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -537,11 +532,6 @@
<item name='java.net.URI java.lang.String normalize(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.net.URI java.lang.String quote(java.lang.String, long, long)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.net.URI java.lang.String quote(java.lang.String, long, long) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -586,30 +576,15 @@
<item name='java.net.URI java.net.URI create(java.lang.String)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.net.URI java.net.URI normalize(java.net.URI)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.net.URI java.net.URI normalize(java.net.URI) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.net.URI java.net.URI parseServerAuthority()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.net.URI java.net.URI relativize(java.net.URI)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.net.URI java.net.URI relativize(java.net.URI) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.net.URI java.net.URI relativize(java.net.URI, java.net.URI)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;_,!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.net.URI java.net.URI relativize(java.net.URI, java.net.URI) 1'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -1673,11 +1673,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collections.EmptyList T[] toArray(T[])'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.util.Collections.EmptyList T[] toArray(T[]) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -1861,11 +1856,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.util.Collections.EmptySet T[] toArray(T[])'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.util.Collections.EmptySet T[] toArray(T[]) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -3119,19 +3119,9 @@
<item name='org.apache.commons.lang.StringUtils java.lang.String chomp(java.lang.String, java.lang.String) 1'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String chompLast(java.lang.String, java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -3217,11 +3207,6 @@
<item name='org.apache.commons.lang.StringUtils java.lang.String escape(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String getChomp(java.lang.String, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;_,!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String getChomp(java.lang.String, java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -3451,11 +3436,6 @@
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String prechomp(java.lang.String, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String prechomp(java.lang.String, java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -285,17 +285,12 @@
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction add(org.apache.commons.lang.math.Fraction)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction add(org.apache.commons.lang.math.Fraction) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction addSub(org.apache.commons.lang.math.Fraction, boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;!null;_,false-&gt;!null&quot;"/>
<val val="&quot;_,false-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction addSub(org.apache.commons.lang.math.Fraction, boolean) 0'>
@@ -359,11 +354,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction subtract(org.apache.commons.lang.math.Fraction)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction subtract(org.apache.commons.lang.math.Fraction) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -82,11 +82,6 @@
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.velocity.app.event.implement.IncludeRelativePath java.lang.String includeEvent(java.lang.String, java.lang.String, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.app.event.implement.IncludeRelativePath java.lang.String includeEvent(java.lang.String, java.lang.String, java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -34,11 +34,6 @@
<item name='org.apache.velocity.app.tools.VelocityFormatter java.lang.String formatVector(java.util.List, java.lang.String, java.lang.String) 2'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='org.apache.velocity.app.tools.VelocityFormatter java.lang.String limitLen(int, java.lang.String, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;_,!null,_-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.app.tools.VelocityFormatter java.lang.String limitLen(int, java.lang.String, java.lang.String) 1'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -15,11 +15,6 @@
<item name='org.apache.velocity.convert.WebMacro boolean writeTemplate(java.lang.String, java.lang.String, java.lang.String) 2'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='org.apache.velocity.convert.WebMacro java.lang.String convertName(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.convert.WebMacro java.lang.String convertName(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -32,11 +32,6 @@
<item name='org.apache.velocity.runtime.parser.Parser Parser(org.apache.velocity.runtime.RuntimeServices) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.velocity.runtime.parser.Parser java.lang.String escapedDirective(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.Parser java.lang.String escapedDirective(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -169,11 +169,6 @@
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='org.apache.velocity.util.StringUtils java.lang.String sub(java.lang.String, java.lang.String, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.util.StringUtils java.lang.String sub(java.lang.String, java.lang.String, java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>

View File

@@ -222,7 +222,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
*/
HKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out, myMessageDigest);
EKey psiKey = BytecodeAnalysisConverter.psiKey(psiMethod, Direction.Out, myMessageDigest);
if (noKey) {
assertTrue(null == psiKey);
return;
@@ -230,7 +230,7 @@ public class BytecodeAnalysisTest extends JavaCodeInsightFixtureTestCase {
else {
assertFalse(null == psiKey);
}
HKey asmKey = BytecodeAnalysisConverter.asmKey(new Key(method, Direction.Out, true), myMessageDigest);
EKey asmKey = new EKey(method, Direction.Out, true).hashed(myMessageDigest);
Assert.assertEquals(asmKey, psiKey);
}