mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
inference of @Nullable annotations for parameters
This commit is contained in:
+4
-25
@@ -329,14 +329,14 @@ public class BytecodeAnalysisConverter {
|
||||
* Given `solution` of all dependencies of a method with the `methodKey`, converts this solution into annotations.
|
||||
*
|
||||
* @param solution solution of equations
|
||||
* @param annotations annotations to which corresponding solutions should be added
|
||||
* @param methodAnnotations annotations to which corresponding solutions should be added
|
||||
* @param methodKey a primary key of a method being analyzed
|
||||
* @param arity arity of this method (hint for constructing @Contract annotations)
|
||||
*/
|
||||
public static void addMethodAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull Annotations annotations, @NotNull HKey methodKey, int arity) {
|
||||
public static void addMethodAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull MethodAnnotations methodAnnotations, @NotNull HKey methodKey, int arity) {
|
||||
List<String> clauses = new ArrayList<String>();
|
||||
HashSet<HKey> notNulls = annotations.notNulls;
|
||||
HashMap<HKey, String> contracts = annotations.contracts;
|
||||
HashSet<HKey> notNulls = methodAnnotations.notNulls;
|
||||
HashMap<HKey, String> contracts = methodAnnotations.contracts;
|
||||
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
|
||||
HKey key = entry.getKey().mkStable();
|
||||
Value value = entry.getValue();
|
||||
@@ -364,27 +364,6 @@ public class BytecodeAnalysisConverter {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts solutions for equations over parameters into annotations.
|
||||
*
|
||||
* @param solution
|
||||
* @param annotations
|
||||
*/
|
||||
public static void addParameterAnnotations(@NotNull HashMap<HKey, Value> solution, @NotNull Annotations annotations) {
|
||||
HashSet<HKey> notNulls = annotations.notNulls;
|
||||
for (Map.Entry<HKey, Value> entry : solution.entrySet()) {
|
||||
HKey key = entry.getKey().mkStable();
|
||||
Value value = entry.getValue();
|
||||
if (value == Value.Top || value == Value.Bot) {
|
||||
continue;
|
||||
}
|
||||
Direction direction = extractDirection(key.dirKey);
|
||||
if (value == Value.NotNull && (direction instanceof In || direction instanceof Out)) {
|
||||
notNulls.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String contractValueString(@NotNull Value v) {
|
||||
switch (v) {
|
||||
case False: return "false";
|
||||
|
||||
+26
-8
@@ -42,6 +42,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
public static final Final<Key, Value> FINAL_TOP = new Final<Key, Value>(Value.Top);
|
||||
public static final Final<Key, Value> FINAL_BOT = new Final<Key, Value>(Value.Bot);
|
||||
public static final Final<Key, Value> FINAL_NOT_NULL = new Final<Key, Value>(Value.NotNull);
|
||||
public static final Final<Key, Value> FINAL_NULL = new Final<Key, Value>(Value.Null);
|
||||
private static final List<Equation<Key, Value>> EMPTY_EQUATIONS = Collections.EMPTY_LIST;
|
||||
|
||||
@NotNull
|
||||
@@ -129,22 +130,21 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
return Pair.create(primaryKey, EMPTY_EQUATIONS);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
final ControlFlowGraph graph = ControlFlowGraph.build(className, methodNode, jsr);
|
||||
if (graph.transitions.length > 0) {
|
||||
final DFSTree dfs = DFSTree.build(graph.transitions, graph.edgeCount);
|
||||
boolean complex = !dfs.back.isEmpty();
|
||||
if (!complex) {
|
||||
boolean branching = !dfs.back.isEmpty();
|
||||
if (!branching) {
|
||||
for (int[] transition : graph.transitions) {
|
||||
if (transition != null && transition.length > 1) {
|
||||
complex = true;
|
||||
branching = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (complex) {
|
||||
if (branching) {
|
||||
RichControlFlow richControlFlow = new RichControlFlow(graph, dfs);
|
||||
if (richControlFlow.reducible()) {
|
||||
return Pair.create(primaryKey,
|
||||
@@ -180,7 +180,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
final boolean stable,
|
||||
boolean jsr) throws AnalyzerException {
|
||||
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 3 + 1);
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 1);
|
||||
boolean maybeLeakingParameter = isInterestingResult;
|
||||
for (Type argType : argumentTypes) {
|
||||
if (ASMUtils.isReferenceType(argType) || (isReferenceResult && ASMUtils.isBooleanType(argType))) {
|
||||
@@ -193,6 +193,8 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
maybeLeakingParameter ? leakingParametersAndFrames(method, methodNode, argumentTypes, jsr) : null;
|
||||
boolean[] leakingParameters =
|
||||
leakingParametersAndFrames != null ? leakingParametersAndFrames.parameters : null;
|
||||
boolean[] leakingNullableParameters =
|
||||
leakingParametersAndFrames != null ? leakingParametersAndFrames.nullableParameters : null;
|
||||
|
||||
final NullableLazyValue<boolean[]> origins = new NullableLazyValue<boolean[]>() {
|
||||
@Override
|
||||
@@ -231,8 +233,11 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
boolean notNullParam = false;
|
||||
|
||||
if (isReferenceArg) {
|
||||
boolean possibleNPE = false;
|
||||
if (leakingParameters[i]) {
|
||||
Equation<Key, Value> notNullParamEquation = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL), stable).analyze();
|
||||
NonNullInAnalysis notNullInAnalysis = new NonNullInAnalysis(richControlFlow, new In(i, In.NOT_NULL), stable);
|
||||
Equation<Key, Value> notNullParamEquation = notNullInAnalysis.analyze();
|
||||
possibleNPE = notNullInAnalysis.possibleNPE;
|
||||
notNullParam = notNullParamEquation.rhs.equals(FINAL_NOT_NULL);
|
||||
result.add(notNullParamEquation);
|
||||
}
|
||||
@@ -240,6 +245,17 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
// parameter is not leaking, so it is definitely NOT @NotNull
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
|
||||
}
|
||||
if (leakingNullableParameters[i]) {
|
||||
if (notNullParam || possibleNPE) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
|
||||
}
|
||||
else {
|
||||
result.add(new NullableInAnalysis(richControlFlow, new In(i, In.NULLABLE), stable).analyze());
|
||||
}
|
||||
}
|
||||
else {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_NULL));
|
||||
}
|
||||
}
|
||||
if (isReferenceArg && isInterestingResult) {
|
||||
if (leakingParameters[i]) {
|
||||
@@ -296,7 +312,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
boolean isReferenceResult,
|
||||
boolean isBooleanResult,
|
||||
boolean stable) throws AnalyzerException {
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 3 + 1);
|
||||
List<Equation<Key, Value>> result = new ArrayList<Equation<Key, Value>>(argumentTypes.length * 4 + 1);
|
||||
CombinedSingleAnalysis analyzer = new CombinedSingleAnalysis(method, graph);
|
||||
analyzer.analyze();
|
||||
if (isReferenceResult) {
|
||||
@@ -307,6 +323,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
boolean isRefArg = ASMUtils.isReferenceType(argType);
|
||||
if (isRefArg) {
|
||||
result.add(analyzer.notNullParamEquation(i, stable));
|
||||
result.add(analyzer.nullableParamEquation(i, stable));
|
||||
}
|
||||
if (isRefArg && (isReferenceResult || isBooleanResult)) {
|
||||
result.add(analyzer.contractEquation(i, Value.Null, stable));
|
||||
@@ -336,6 +353,7 @@ public class ClassDataIndexer implements DataIndexer<Bytes, HEquations, FileCont
|
||||
|
||||
if (isReferenceArg) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NOT_NULL), stable), FINAL_TOP));
|
||||
result.add(new Equation<Key, Value>(new Key(method, new In(i, In.NULLABLE), stable), FINAL_TOP));
|
||||
}
|
||||
if (isReferenceArg && isInterestingResult) {
|
||||
result.add(new Equation<Key, Value>(new Key(method, new InOut(i, Value.Null), stable), FINAL_TOP));
|
||||
|
||||
+91
-10
@@ -28,12 +28,48 @@ 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.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*;
|
||||
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
|
||||
final class ParamKey {
|
||||
final Method method;
|
||||
final int i;
|
||||
final boolean stable;
|
||||
|
||||
|
||||
ParamKey(Method method, int i, boolean stable) {
|
||||
this.method = method;
|
||||
this.i = i;
|
||||
this.stable = stable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
ParamKey paramKey = (ParamKey)o;
|
||||
|
||||
if (i != paramKey.i) return false;
|
||||
if (stable != paramKey.stable) return false;
|
||||
if (!method.equals(paramKey.method)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = method.hashCode();
|
||||
result = 31 * result + i;
|
||||
result = 31 * result + (stable ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
final class CombinedCall extends BasicValue {
|
||||
final Method method;
|
||||
final boolean stableCall;
|
||||
@@ -112,12 +148,38 @@ final class CombinedSingleAnalysis {
|
||||
result = new Final<Key, Value>(Value.NotNull);
|
||||
}
|
||||
else {
|
||||
Set<Key> calls = interpreter.callDerefs[i];
|
||||
Set<ParamKey> calls = interpreter.callDerefs[i];
|
||||
if (calls == null || calls.isEmpty()) {
|
||||
result = new Final<Key, Value>(Value.Top);
|
||||
}
|
||||
else {
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, calls)));
|
||||
Set<Key> keys = new HashSet<Key>();
|
||||
for (ParamKey pk: calls) {
|
||||
keys.add(new Key(pk.method, new In(pk.i, In.NOT_NULL), pk.stable));
|
||||
}
|
||||
result = new Pending<Key, Value>(new SingletonSet<Product<Key, Value>>(new Product<Key, Value>(Value.Top, keys)));
|
||||
}
|
||||
}
|
||||
return new Equation<Key, Value>(key, result);
|
||||
}
|
||||
|
||||
final Equation<Key, Value> nullableParamEquation(int i, boolean stable) {
|
||||
final Key key = new Key(method, new In(i, In.NULLABLE), stable);
|
||||
final Result<Key, Value> result;
|
||||
if (interpreter.dereferenced[i] || interpreter.notNullable[i] || returnValue instanceof NParamValue && ((NParamValue)returnValue).n == i) {
|
||||
result = new Final<Key, Value>(Value.Top);
|
||||
}
|
||||
else {
|
||||
Set<ParamKey> calls = interpreter.callDerefs[i];
|
||||
if (calls == null || calls.isEmpty()) {
|
||||
result = new Final<Key, Value>(Value.Null);
|
||||
}
|
||||
else {
|
||||
Set<Product<Key, Value>> sum = new HashSet<Product<Key, Value>>();
|
||||
for (ParamKey pk: calls) {
|
||||
sum.add(new Product<Key, Value>(Value.Top, Collections.singleton(new Key(pk.method, new In(pk.i, In.NULLABLE), pk.stable))));
|
||||
}
|
||||
result = new Pending<Key, Value>(sum);
|
||||
}
|
||||
}
|
||||
return new Equation<Key, Value>(key, result);
|
||||
@@ -228,10 +290,12 @@ final class CombinedSingleAnalysis {
|
||||
|
||||
final class CombinedInterpreter extends BasicInterpreter {
|
||||
final boolean[] dereferenced;
|
||||
final Set<Key>[] callDerefs;
|
||||
final boolean[] notNullable;
|
||||
final Set<ParamKey>[] callDerefs;
|
||||
|
||||
CombinedInterpreter(int arity) {
|
||||
dereferenced = new boolean[arity];
|
||||
notNullable = new boolean[arity];
|
||||
callDerefs = new Set[arity];
|
||||
}
|
||||
|
||||
@@ -303,10 +367,17 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
if (value1 instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)value1).n] = true;
|
||||
}
|
||||
break;
|
||||
case PUTFIELD:
|
||||
if (value1 instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)value1).n] = true;
|
||||
}
|
||||
if (value2 instanceof NParamValue) {
|
||||
notNullable[((NParamValue)value2).n] = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -321,13 +392,21 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
if (value1 instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)value1).n] = true;
|
||||
}
|
||||
break;
|
||||
case AASTORE:
|
||||
if (value1 instanceof NParamValue) {
|
||||
dereferenced[((NParamValue)value1).n] = true;
|
||||
}
|
||||
if (value3 instanceof NParamValue) {
|
||||
notNullable[((NParamValue)value3).n] = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
@@ -347,7 +426,6 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (opCode) {
|
||||
case INVOKESTATIC:
|
||||
case INVOKESPECIAL:
|
||||
@@ -360,14 +438,17 @@ final class CombinedInterpreter extends BasicInterpreter {
|
||||
|
||||
for (int i = shift; i < values.size(); i++) {
|
||||
if (values.get(i) instanceof NParamValue) {
|
||||
if (opCode != INVOKEINTERFACE) {
|
||||
int n = ((NParamValue)values.get(i)).n;
|
||||
Set<Key> npKeys = callDerefs[n];
|
||||
int n = ((NParamValue)values.get(i)).n;
|
||||
if (opCode == INVOKEINTERFACE) {
|
||||
notNullable[n] = true;
|
||||
}
|
||||
else {
|
||||
Set<ParamKey> npKeys = callDerefs[n];
|
||||
if (npKeys == null) {
|
||||
npKeys = new HashSet<Key>();
|
||||
npKeys = new HashSet<ParamKey>();
|
||||
callDerefs[n] = npKeys;
|
||||
}
|
||||
npKeys.add(new Key(method, new In(i - shift, In.NOT_NULL), stable));
|
||||
npKeys.add(new ParamKey(method, i - shift, stable));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ final class HKey {
|
||||
return stable ? this : new HKey(key, dirKey, true);
|
||||
}
|
||||
|
||||
HKey mkUnstable() {
|
||||
return stable ? new HKey(key, dirKey, false) : this;
|
||||
}
|
||||
|
||||
public HKey mkBase() {
|
||||
return dirKey == 0 ? this : new HKey(key, 0, stable);
|
||||
}
|
||||
|
||||
+9
-6
@@ -116,7 +116,7 @@ abstract class PResults {
|
||||
if (NPE == r2) return NPE;
|
||||
ConditionalNPE cnpe1 = (ConditionalNPE) r1;
|
||||
ConditionalNPE cnpe2 = (ConditionalNPE) r2;
|
||||
return new ConditionalNPE(meet(cnpe1.sop, cnpe2.sop));
|
||||
return new ConditionalNPE(join(cnpe1.sop, cnpe2.sop));
|
||||
}
|
||||
|
||||
static PResult join(PResult r1, PResult r2) throws AnalyzerException {
|
||||
@@ -184,6 +184,9 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
private final NotNullInterpreter interpreter = new NotNullInterpreter();
|
||||
private PResult[] results;
|
||||
|
||||
// Flag saying that at some branch NPE was found. Used later as an evidence that this param is *NOT* @Nullable (optimization).
|
||||
boolean possibleNPE;
|
||||
|
||||
protected NonNullInAnalysis(RichControlFlow richControlFlow, Direction direction, boolean stable) {
|
||||
super(richControlFlow, direction, stable);
|
||||
results = ourResults.get();
|
||||
@@ -301,6 +304,7 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
|
||||
if (subResult == NPE) {
|
||||
results[stateIndex] = NPE;
|
||||
possibleNPE = true;
|
||||
addComputed(insnIndex, state);
|
||||
return;
|
||||
}
|
||||
@@ -326,6 +330,7 @@ class NonNullInAnalysis extends Analysis<PResult> {
|
||||
if (opcode == ATHROW) {
|
||||
if (taken) {
|
||||
results[stateIndex] = NPE;
|
||||
possibleNPE = true;
|
||||
} else {
|
||||
results[stateIndex] = Identity;
|
||||
}
|
||||
@@ -481,13 +486,9 @@ class NullableInAnalysis extends Analysis<PResult> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (baseState != null) {
|
||||
addComputed(insnIndex, state);
|
||||
} else {
|
||||
// the main call
|
||||
if (baseState == null) {
|
||||
processState(state);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (earlyResult != null) {
|
||||
@@ -505,6 +506,8 @@ class NullableInAnalysis extends Analysis<PResult> {
|
||||
Frame<BasicValue> frame = conf.frame;
|
||||
AbstractInsnNode insnNode = methodNode.instructions.get(insnIndex);
|
||||
List<Conf> nextHistory = dfsTree.loopEnters[insnIndex] ? append(history, conf) : history;
|
||||
|
||||
addComputed(insnIndex, state);
|
||||
execute(frame, insnNode);
|
||||
|
||||
if (subResult == NPE || top) {
|
||||
|
||||
+88
-53
@@ -37,10 +37,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* @author lambdamix
|
||||
@@ -64,7 +61,7 @@ public class ProjectBytecodeAnalysis {
|
||||
if (!(listOwner instanceof PsiCompiledElement)) {
|
||||
return null;
|
||||
}
|
||||
if (annotationFQN.equals(AnnotationUtil.NOT_NULL) || annotationFQN.equals(ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) {
|
||||
if (annotationFQN.equals(AnnotationUtil.NOT_NULL) || annotationFQN.equals(AnnotationUtil.NULLABLE) || annotationFQN.equals(ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT)) {
|
||||
PsiAnnotation[] annotations = findInferredAnnotations(listOwner);
|
||||
for (PsiAnnotation annotation : annotations) {
|
||||
if (annotationFQN.equals(annotation.getQualifiedName())) {
|
||||
@@ -96,34 +93,45 @@ public class ProjectBytecodeAnalysis {
|
||||
private PsiAnnotation[] collectInferredAnnotations(PsiModifierListOwner listOwner) {
|
||||
try {
|
||||
MessageDigest md = BytecodeAnalysisConverter.getMessageDigest();
|
||||
HKey ownerKey = getKey(listOwner, md);
|
||||
if (ownerKey == null) {
|
||||
HKey primaryKey = getKey(listOwner, md);
|
||||
if (primaryKey == null) {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
ArrayList<HKey> allKeys = contractKeys(listOwner, ownerKey);
|
||||
Annotations annotations = loadAnnotations(listOwner, ownerKey, allKeys);
|
||||
boolean notNull = annotations.notNulls.contains(ownerKey);
|
||||
String contractValue = annotations.contracts.get(ownerKey);
|
||||
|
||||
if (notNull && contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation(),
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
else if (notNull) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation()
|
||||
};
|
||||
}
|
||||
else if (contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
else {
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
if (listOwner instanceof PsiMethod) {
|
||||
ArrayList<HKey> allKeys = contractKeys((PsiMethod)listOwner, primaryKey);
|
||||
MethodAnnotations methodAnnotations = loadMethodAnnotations((PsiMethod)listOwner, primaryKey, allKeys);
|
||||
boolean notNull = methodAnnotations.notNulls.contains(primaryKey);
|
||||
String contractValue = methodAnnotations.contracts.get(primaryKey);
|
||||
if (notNull && contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation(),
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
else if (notNull) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation()
|
||||
};
|
||||
}
|
||||
else if (contractValue != null) {
|
||||
return new PsiAnnotation[]{
|
||||
createAnnotationFromText("@" + ControlFlowAnalyzer.ORG_JETBRAINS_ANNOTATIONS_CONTRACT + "(" + contractValue + ")")
|
||||
};
|
||||
}
|
||||
} else if (listOwner instanceof PsiParameter) {
|
||||
ParameterAnnotations parameterAnnotations = loadParameterAnnotations(primaryKey);
|
||||
if (parameterAnnotations.notNull) {
|
||||
return new PsiAnnotation[]{
|
||||
getNotNullAnnotation()
|
||||
};
|
||||
}
|
||||
else if (parameterAnnotations.nullable) {
|
||||
return new PsiAnnotation[]{
|
||||
getNullableAnnotation()
|
||||
};
|
||||
}
|
||||
}
|
||||
return PsiAnnotation.EMPTY_ARRAY;
|
||||
}
|
||||
catch (EquationsLimitException e) {
|
||||
String externalName = PsiFormatUtil.getExternalName(listOwner, false, Integer.MAX_VALUE);
|
||||
@@ -146,6 +154,16 @@ public class ProjectBytecodeAnalysis {
|
||||
});
|
||||
}
|
||||
|
||||
private PsiAnnotation getNullableAnnotation() {
|
||||
return CachedValuesManager.getManager(myProject).getCachedValue(myProject, new CachedValueProvider<PsiAnnotation>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public Result<PsiAnnotation> compute() {
|
||||
return Result.create(createAnnotationFromText("@" + AnnotationUtil.NULLABLE), ModificationTracker.NEVER_CHANGED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PsiAnnotation createContractAnnotation(String contractValue) {
|
||||
return createAnnotationFromText("@org.jetbrains.annotations.Contract(" + contractValue + ")");
|
||||
}
|
||||
@@ -169,36 +187,43 @@ public class ProjectBytecodeAnalysis {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ArrayList<HKey> contractKeys(@NotNull PsiModifierListOwner owner, HKey primaryKey) {
|
||||
if (owner instanceof PsiMethod) {
|
||||
ArrayList<HKey> result = BytecodeAnalysisConverter.mkInOutKeys((PsiMethod)owner, primaryKey);
|
||||
result.add(primaryKey);
|
||||
return result;
|
||||
}
|
||||
ArrayList<HKey> result = new ArrayList<HKey>(1);
|
||||
public static ArrayList<HKey> contractKeys(@NotNull PsiMethod owner, HKey primaryKey) {
|
||||
ArrayList<HKey> result = BytecodeAnalysisConverter.mkInOutKeys(owner, primaryKey);
|
||||
result.add(primaryKey);
|
||||
return result;
|
||||
}
|
||||
|
||||
private Annotations loadAnnotations(@NotNull PsiModifierListOwner owner, @NotNull HKey key, ArrayList<HKey> allKeys)
|
||||
private ParameterAnnotations loadParameterAnnotations(@NotNull HKey notNullKey)
|
||||
throws EquationsLimitException {
|
||||
Annotations result = new Annotations();
|
||||
if (owner instanceof PsiParameter) {
|
||||
final Solver solver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top));
|
||||
collectEquations(allKeys, solver);
|
||||
HashMap<HKey, Value> solutions = solver.solve();
|
||||
BytecodeAnalysisConverter.addParameterAnnotations(solutions, result);
|
||||
} else if (owner instanceof PsiMethod) {
|
||||
final Solver solver = new Solver(new ELattice<Value>(Value.Bot, Value.Top));
|
||||
collectEquations(allKeys, solver);
|
||||
HashMap<HKey, Value> solutions = solver.solve();
|
||||
int arity = ((PsiMethod)owner).getParameterList().getParameters().length;
|
||||
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
|
||||
}
|
||||
|
||||
final Solver notNullSolver = new Solver(new ELattice<Value>(Value.NotNull, Value.Top));
|
||||
collectEquations(Collections.singletonList(notNullKey), notNullSolver);
|
||||
|
||||
HashMap<HKey, Value> notNullSolutions = notNullSolver.solve();
|
||||
boolean notNull =
|
||||
(Value.NotNull == notNullSolutions.get(notNullKey)) || (Value.NotNull == notNullSolutions.get(notNullKey.mkUnstable()));
|
||||
|
||||
final Solver nullableSolver = new Solver(new ELattice<Value>(Value.Null, Value.Top));
|
||||
final HKey nullableKey = new HKey(notNullKey.key, notNullKey.dirKey + 1, true);
|
||||
collectEquations(Collections.singletonList(nullableKey), nullableSolver);
|
||||
HashMap<HKey, Value> nullableSolutions = nullableSolver.solve();
|
||||
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)
|
||||
throws EquationsLimitException {
|
||||
MethodAnnotations result = new MethodAnnotations();
|
||||
final Solver solver = new Solver(new ELattice<Value>(Value.Bot, Value.Top));
|
||||
collectEquations(allKeys, solver);
|
||||
HashMap<HKey, Value> solutions = solver.solve();
|
||||
int arity = owner.getParameterList().getParameters().length;
|
||||
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void collectEquations(ArrayList<HKey> keys, Solver solver) throws EquationsLimitException {
|
||||
private void collectEquations(List<HKey> keys, Solver solver) throws EquationsLimitException {
|
||||
GlobalSearchScope librariesScope = ProjectScope.getLibrariesScope(myProject);
|
||||
HashSet<HKey> queued = new HashSet<HKey>();
|
||||
Stack<HKey> queue = new Stack<HKey>();
|
||||
@@ -258,11 +283,21 @@ public class ProjectBytecodeAnalysis {
|
||||
}
|
||||
}
|
||||
|
||||
class Annotations {
|
||||
class MethodAnnotations {
|
||||
// @NotNull keys
|
||||
final HashSet<HKey> notNulls = new HashSet<HKey>();
|
||||
// @Contracts
|
||||
final HashMap<HKey, String> contracts = new HashMap<HKey, String>();
|
||||
}
|
||||
|
||||
class ParameterAnnotations {
|
||||
final boolean notNull;
|
||||
final boolean nullable;
|
||||
|
||||
ParameterAnnotations(boolean notNull, boolean nullable) {
|
||||
this.notNull = notNull;
|
||||
this.nullable = nullable;
|
||||
}
|
||||
}
|
||||
|
||||
class EquationsLimitException extends Exception {}
|
||||
|
||||
+47
-9
@@ -31,10 +31,12 @@ import static org.jetbrains.org.objectweb.asm.Opcodes.*;
|
||||
public class LeakingParameters {
|
||||
public final Frame<Value>[] frames;
|
||||
public final boolean[] parameters;
|
||||
public final boolean[] nullableParameters;
|
||||
|
||||
public LeakingParameters(Frame<Value>[] frames, boolean[] parameters) {
|
||||
public LeakingParameters(Frame<Value>[] frames, boolean[] parameters, boolean[] nullableParameters) {
|
||||
this.frames = frames;
|
||||
this.parameters = parameters;
|
||||
this.nullableParameters = nullableParameters;
|
||||
}
|
||||
|
||||
public static LeakingParameters build(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException {
|
||||
@@ -57,7 +59,12 @@ public class LeakingParameters {
|
||||
}
|
||||
}
|
||||
}
|
||||
return new LeakingParameters((Frame<Value>[])(Frame<?>[])frames, collector.leaking);
|
||||
boolean[] notNullParameters = collector.leaking;
|
||||
boolean[] nullableParameters = collector.nullableLeaking;
|
||||
for (int i = 0; i < nullableParameters.length; i++) {
|
||||
nullableParameters[i] |= notNullParameters[i];
|
||||
}
|
||||
return new LeakingParameters((Frame<Value>[])(Frame<?>[])frames, notNullParameters, nullableParameters);
|
||||
}
|
||||
|
||||
public static LeakingParameters buildFast(String className, MethodNode methodNode, boolean jsr) throws AnalyzerException {
|
||||
@@ -66,11 +73,14 @@ public class LeakingParameters {
|
||||
new Analyzer<IParamsValue>(parametersUsage).analyze(className, methodNode) :
|
||||
new LiteAnalyzer<IParamsValue>(parametersUsage).analyze(className, methodNode);
|
||||
int leakingMask = parametersUsage.leaking;
|
||||
boolean[] result = new boolean[parametersUsage.arity];
|
||||
for (int i = 0; i < result.length; i++) {
|
||||
result[i] = (leakingMask & (1 << i)) != 0;
|
||||
int nullableLeakingMask = parametersUsage.nullableLeaking;
|
||||
boolean[] notNullParameters = new boolean[parametersUsage.arity];
|
||||
boolean[] nullableParameters = new boolean[parametersUsage.arity];
|
||||
for (int i = 0; i < notNullParameters.length; i++) {
|
||||
notNullParameters[i] = (leakingMask & (1 << i)) != 0;
|
||||
nullableParameters[i] = ((leakingMask | nullableLeakingMask) & (1 << i)) != 0;
|
||||
}
|
||||
return new LeakingParameters((Frame<Value>[])frames, result);
|
||||
return new LeakingParameters((Frame<Value>[])frames, notNullParameters, nullableParameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,6 +298,7 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
static final IParamsValue val1 = new IParamsValue(0, 1);
|
||||
static final IParamsValue val2 = new IParamsValue(0, 2);
|
||||
int leaking = 0;
|
||||
int nullableLeaking = 0;
|
||||
int called = -1;
|
||||
final int rangeStart;
|
||||
final int rangeEnd;
|
||||
@@ -415,8 +426,12 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
leaking |= value1.params;
|
||||
size = 1;
|
||||
break;
|
||||
case PUTFIELD:
|
||||
leaking |= value1.params;
|
||||
nullableLeaking |= value2.params;
|
||||
size = 1;
|
||||
break;
|
||||
default:
|
||||
@@ -432,12 +447,15 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
leaking |= value1.params;
|
||||
break;
|
||||
case AASTORE:
|
||||
leaking |= value1.params;
|
||||
nullableLeaking |= value3.params;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return val1;
|
||||
@@ -479,9 +497,11 @@ class IParametersUsage extends Interpreter<IParamsValue> {
|
||||
|
||||
class LeakingParametersCollector extends ParametersUsage {
|
||||
final boolean[] leaking;
|
||||
final boolean[] nullableLeaking;
|
||||
LeakingParametersCollector(MethodNode methodNode) {
|
||||
super(methodNode);
|
||||
leaking = new boolean[arity];
|
||||
nullableLeaking = new boolean[arity];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -518,12 +538,21 @@ class LeakingParametersCollector extends ParametersUsage {
|
||||
case BALOAD:
|
||||
case CALOAD:
|
||||
case SALOAD:
|
||||
case PUTFIELD:
|
||||
boolean[] params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
case PUTFIELD:
|
||||
params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
params = value2.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
nullableLeaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.binaryOperation(insn, value1, value2);
|
||||
@@ -536,7 +565,6 @@ class LeakingParametersCollector extends ParametersUsage {
|
||||
case LASTORE:
|
||||
case FASTORE:
|
||||
case DASTORE:
|
||||
case AASTORE:
|
||||
case BASTORE:
|
||||
case CASTORE:
|
||||
case SASTORE:
|
||||
@@ -545,6 +573,16 @@ class LeakingParametersCollector extends ParametersUsage {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
case AASTORE:
|
||||
params = value1.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
leaking[i] |= params[i];
|
||||
}
|
||||
params = value3.params;
|
||||
for (int i = 0; i < arity; i++) {
|
||||
nullableLeaking[i] |= params[i];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
}
|
||||
return super.ternaryOperation(insn, value1, value2, value3);
|
||||
|
||||
Reference in New Issue
Block a user