mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
BytecodeAnalysis refactoring: HKey and Key merged to EKey; hashing is encapsulated inside HMethod/Method pair
This commit is contained in:
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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") + ")";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) + "]";
|
||||
}
|
||||
}
|
||||
@@ -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(".");
|
||||
}
|
||||
}
|
||||
@@ -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(".");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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=""!null,_->!null""/>
|
||||
<val name="pure" val="true"/>
|
||||
</annotation>
|
||||
</item>
|
||||
|
||||
@@ -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=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.lang.invoke.BoundMethodHandle java.lang.String noParens(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
@@ -486,11 +486,6 @@
|
||||
<val val=""!null->!null;null->null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.net.URI java.lang.String encode(java.lang.String)'>
|
||||
<annotation name='org.jetbrains.annotations.Contract'>
|
||||
<val val=""!null->!null""/>
|
||||
</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=""!null,_,_->!null""/>
|
||||
</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=""!null->!null""/>
|
||||
</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=""!null->!null""/>
|
||||
</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=""_,!null->!null""/>
|
||||
</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>
|
||||
|
||||
@@ -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=""!null->!null""/>
|
||||
</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=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='java.util.Collections.EmptySet T[] toArray(T[]) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
@@ -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=""!null->!null""/>
|
||||
</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=""!null,_->!null""/>
|
||||
</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=""_,!null->!null""/>
|
||||
</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=""!null,_->!null""/>
|
||||
</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>
|
||||
|
||||
@@ -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=""!null->!null""/>
|
||||
</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=""!null,_->!null;_,false->!null""/>
|
||||
<val val=""_,false->!null""/>
|
||||
</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=""!null->!null""/>
|
||||
</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>
|
||||
|
||||
@@ -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=""!null,_,_->!null""/>
|
||||
</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>
|
||||
|
||||
@@ -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=""_,!null,_->!null""/>
|
||||
</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>
|
||||
|
||||
@@ -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=""!null->!null""/>
|
||||
</annotation>
|
||||
</item>
|
||||
<item name='org.apache.velocity.convert.WebMacro java.lang.String convertName(java.lang.String) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
|
||||
@@ -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=""!null->!null""/>
|
||||
</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>
|
||||
|
||||
@@ -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=""!null,_,_->!null""/>
|
||||
</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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user