inference of @Nullable annotations for parameters

This commit is contained in:
Ilya Klyuchnikov
2014-08-25 18:30:42 +04:00
parent e88755962b
commit 8eb3633fb3
7 changed files with 269 additions and 111 deletions
@@ -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";
@@ -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));
@@ -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);
}
@@ -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) {
@@ -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 {}
@@ -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);