BytecodeAnalysis: infer failing contracts; infer contracts for input boolean arguments; squash inferred contracts; refactoring

This commit is contained in:
Tagir Valeev
2017-05-24 11:49:05 +07:00
parent 871ef095bb
commit 5599cc543f
35 changed files with 764 additions and 419 deletions

View File

@@ -30,9 +30,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.In;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.InOut;
class AbstractValues {
static final class ParamValue extends BasicValue {
ParamValue(Type tp) {
@@ -79,6 +76,15 @@ class AbstractValues {
}
}
static final class NthParamValue extends BasicValue {
final int n;
public NthParamValue(Type type, int n) {
super(type);
this.n = n;
}
}
static final BasicValue CLASS_VALUE = new NotNullValue(Type.getObjectType("java/lang/Class"));
static final BasicValue METHOD_VALUE = new NotNullValue(Type.getObjectType("java/lang/invoke/MethodType"));
static final BasicValue STRING_VALUE = new NotNullValue(Type.getObjectType("java/lang/String"));
@@ -272,12 +278,11 @@ abstract class Analysis<Res> {
}
for (int i = 0; i < args.length; i++) {
BasicValue value;
if (direction instanceof InOut && ((InOut)direction).paramIndex == i ||
direction instanceof In && ((In)direction).paramIndex == i) {
if (direction instanceof Direction.ParamIdBasedDirection && ((Direction.ParamIdBasedDirection)direction).paramIndex == i) {
value = new AbstractValues.ParamValue(args[i]);
}
else {
value = new BasicValue(args[i]);
value = new AbstractValues.NthParamValue(args[i], i);
}
frame.setLocal(local++, value);
if (args[i].getSize() == 2) {

View File

@@ -15,13 +15,15 @@
*/
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.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.TypeConversionUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -114,7 +116,7 @@ public class BytecodeAnalysisConverter {
}
hResult = new HEffects(hEffects);
}
return new DirectionResultPair(mkDirectionKey(equation.id.direction), hResult);
return new DirectionResultPair(equation.id.direction.asInt(), hResult);
}
/**
@@ -129,7 +131,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, mkDirectionKey(key.direction), key.stable, key.negated);
return new HKey(digest, key.direction.asInt(), key.stable, key.negated);
}
/**
@@ -153,7 +155,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, mkDirectionKey(direction), true, false);
return new HKey(digest, direction.asInt(), true, false);
}
@Nullable
@@ -272,7 +274,7 @@ public class BytecodeAnalysisConverter {
return descriptor(psiClass, dimensions, true);
}
else {
LOG.debug("resolve was null for " + ((PsiClassType)psiType).getClassName());
LOG.debug("resolve was null for " + psiType.getCanonicalText());
return null;
}
}
@@ -314,79 +316,6 @@ public class BytecodeAnalysisConverter {
}
/**
* Converts Direction object to int.
*
* 0 - Out
* 1 - NullableOut
* 2 - Pure
*
* 3 - 0-th NOT_NULL
* 4 - 0-th NULLABLE
* ...
*
* 11 - 1-st NOT_NULL
* 12 - 1-st NULLABLE
*
* @param dir direction of analysis
* @return unique int for direction
*/
static int mkDirectionKey(Direction dir) {
if (dir == Out) {
return 0;
}
else if (dir == NullableOut) {
return 1;
}
else if (dir == Pure) {
return 2;
}
else if (dir instanceof In) {
In in = (In)dir;
// nullity mask is 0/1
return 3 + 8 * in.paramId() + in.nullityMask;
}
else {
// valueId is [1-5]
InOut inOut = (InOut)dir;
return 3 + 8 * inOut.paramId() + 2 + inOut.valueId();
}
}
/**
* Converts int to Direction object.
*
* @param directionKey int representation of direction
* @return Direction object
* @see #mkDirectionKey(Direction)
*/
@NotNull
static Direction extractDirection(int directionKey) {
if (directionKey == 0) {
return Out;
}
else if (directionKey == 1) {
return NullableOut;
}
else if (directionKey == 2) {
return Pure;
}
else {
int paramKey = directionKey - 3;
int paramId = paramKey / 8;
// shifting first 3 values - now we have key [0 - 7]
int subDirectionId = paramKey % 8;
// 0 - 1 - @NotNull, @Nullable, parameter
if (subDirectionId <= 1) {
return new In(paramId, subDirectionId);
}
else {
int valueId = subDirectionId - 2;
return new InOut(paramId, Value.values()[valueId]);
}
}
}
/**
* Given a PSI method and its primary HKey enumerate all contract keys for it.
*
@@ -401,8 +330,15 @@ public class BytecodeAnalysisConverter {
keys.add(primaryKey);
for (int i = 0; i < parameters.length; i++) {
if (!(parameters[i].getType() instanceof PsiPrimitiveType)) {
keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.NotNull))));
keys.add(primaryKey.updateDirection(mkDirectionKey(new InOut(i, Value.Null))));
keys.add(primaryKey.withDirection(new InOut(i, Value.NotNull)));
keys.add(primaryKey.withDirection(new InOut(i, Value.Null)));
keys.add(primaryKey.withDirection(new InThrow(i, Value.NotNull)));
keys.add(primaryKey.withDirection(new InThrow(i, Value.Null)));
} else if (PsiType.BOOLEAN.equals(parameters[i].getType())) {
keys.add(primaryKey.withDirection(new InOut(i, Value.True)));
keys.add(primaryKey.withDirection(new InOut(i, Value.False)));
keys.add(primaryKey.withDirection(new InThrow(i, Value.True)));
keys.add(primaryKey.withDirection(new InThrow(i, Value.False)));
}
}
return keys;
@@ -417,7 +353,7 @@ public class BytecodeAnalysisConverter {
* @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) {
List<String> contractClauses = new ArrayList<>(arity * 2);
List<StandardMethodContract> contractClauses = new ArrayList<>();
Set<HKey> notNulls = methodAnnotations.notNulls;
Set<HKey> pures = methodAnnotations.pures;
Map<HKey, String> contracts = methodAnnotations.contractsValues;
@@ -425,11 +361,11 @@ public class BytecodeAnalysisConverter {
for (Map.Entry<HKey, 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) {
if (value == Value.Top || value == Value.Bot || (value == Value.Fail && !pures.contains(methodKey))) {
continue;
}
HKey key = entry.getKey().mkStable();
Direction direction = extractDirection(key.dirKey);
Direction direction = key.getDirection();
HKey baseKey = key.mkBase();
if (!methodKey.equals(baseKey)) {
continue;
@@ -440,22 +376,70 @@ public class BytecodeAnalysisConverter {
else if (value == Value.Pure && direction == Pure) {
pures.add(methodKey);
}
else if (direction instanceof InOut) {
contractClauses.add(contractElement(arity, (InOut)direction, value));
else if (direction instanceof ParamValueBasedDirection) {
contractClauses.add(contractElement(arity, (ParamValueBasedDirection)direction, value));
}
}
// no contract clauses for @NotNull methods
if (!notNulls.contains(methodKey) && !contractClauses.isEmpty()) {
// no contract clauses for @NotNull methods
Collections.sort(contractClauses);
StringBuilder sb = new StringBuilder("\"");
StringUtil.join(contractClauses, ";", sb);
sb.append('"');
contracts.put(methodKey, sb.toString().intern());
Map<Boolean, List<StandardMethodContract>> partition =
StreamEx.of(contractClauses).partitioningBy(c -> c.getReturnValue() == ValueConstraint.THROW_EXCEPTION);
List<StandardMethodContract> failingContracts = squashContracts(partition.get(true));
List<StandardMethodContract> nonFailingContracts = squashContracts(partition.get(false));
// Sometimes "null,_->!null;!null,_->!null" contracts are inferred for some reason
// They are squashed to "_,_->!null" which is better expressed as @NotNull annotation
if(nonFailingContracts.size() == 1) {
StandardMethodContract contract = nonFailingContracts.get(0);
if(contract.getReturnValue() == ValueConstraint.NOT_NULL_VALUE && contract.isTrivial()) {
nonFailingContracts = Collections.emptyList();
notNulls.add(methodKey);
}
}
// Failing contracts go first
String result = StreamEx.of(failingContracts, nonFailingContracts)
.flatMap(list -> list.stream()
.map(Object::toString)
.map(str -> str.replace(" ", "")) // for compatibility with existing tests
.sorted())
.joining(";");
if(!result.isEmpty()) {
contracts.put(methodKey, '"'+result+'"');
}
}
}
@NotNull
private static List<StandardMethodContract> squashContracts(List<StandardMethodContract> contractClauses) {
// If there's a pair of contracts yielding the same value like "null,_->true", "!null,_->true"
// then trivial contract should be used like "_,_->true"
StandardMethodContract soleContract = StreamEx.ofPairs(contractClauses, (c1, c2) -> {
if (c1.getReturnValue() != c2.getReturnValue()) return null;
int idx = -1;
for (int i = 0; i < c1.arguments.length; i++) {
ValueConstraint left = c1.arguments[i];
ValueConstraint right = c2.arguments[i];
if(left == ValueConstraint.ANY_VALUE && right == ValueConstraint.ANY_VALUE) continue;
if(idx >= 0) return null;
if(left == ValueConstraint.NOT_NULL_VALUE && right == ValueConstraint.NULL_VALUE ||
left == ValueConstraint.NULL_VALUE && right == ValueConstraint.NOT_NULL_VALUE ||
left == ValueConstraint.TRUE_VALUE && right == ValueConstraint.FALSE_VALUE ||
left == ValueConstraint.FALSE_VALUE && right == ValueConstraint.TRUE_VALUE) {
idx = i;
} else {
return null;
}
}
return c1;
}).nonNull().findFirst().orElse(null);
if(soleContract != null) {
Arrays.fill(soleContract.arguments, ValueConstraint.ANY_VALUE);
contractClauses = Collections.singletonList(soleContract);
}
return contractClauses;
}
public static void addEffectAnnotations(Map<HKey, Set<HEffectQuantum>> puritySolutions,
MethodAnnotations result,
HKey methodKey,
@@ -474,31 +458,11 @@ public class BytecodeAnalysisConverter {
}
}
private static String contractValueString(@NotNull Value v) {
switch (v) {
case False: return "false";
case True: return "true";
case NotNull: return "!null";
case Null: return "null";
default: return "_";
}
}
private static String contractElement(int arity, InOut inOut, Value value) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arity; i++) {
Value currentValue = Value.Top;
if (i == inOut.paramIndex) {
currentValue = inOut.inValue;
}
if (i > 0) {
sb.append(',');
}
sb.append(contractValueString(currentValue));
}
sb.append("->");
sb.append(contractValueString(value));
return sb.toString();
private static StandardMethodContract contractElement(int arity, ParamValueBasedDirection inOut, Value value) {
final ValueConstraint[] constraints = new ValueConstraint[arity];
Arrays.fill(constraints, ValueConstraint.ANY_VALUE);
constraints[inOut.paramIndex] = inOut.inValue.toValueConstraint();
return new StandardMethodContract(constraints, value.toValueConstraint());
}
}

View File

@@ -53,7 +53,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
private static final ID<Bytes, 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", 2, new HEquationsExternalizer(), new ClassDataIndexer());
"BytecodeAnalysisIndex", 3, new HEquationsExternalizer(), new ClassDataIndexer());
@NotNull
@Override
@@ -256,7 +256,7 @@ public class BytecodeAnalysisIndex extends ScalarIndexExtension<Bytes> {
ArrayList<DirectionResultPair> results = new ArrayList<>(size);
for (int k = 0; k < size; k++) {
int directionKey = DataInputOutputUtil.readINT(in);
Direction direction = BytecodeAnalysisConverter.extractDirection(directionKey);
Direction direction = Direction.fromInt(directionKey);
if (direction == Direction.Pure) {
Set<HEffectQuantum> effects = new HashSet<>();
int effectsSize = DataInputOutputUtil.readINT(in);

View File

@@ -22,6 +22,7 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.gist.VirtualFileGist;
import one.util.streamex.EntryStream;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.*;
@@ -29,10 +30,10 @@ import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.stream.Stream;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.*;
import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis.LOG;
@@ -48,6 +49,7 @@ import static com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalys
public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Bytes, HEquations>> {
public static final Final FINAL_TOP = new Final(Value.Top);
public static final Final FINAL_FAIL = new Final(Value.Fail);
public static final Final FINAL_BOT = new Final(Value.Bot);
public static final Final FINAL_NOT_NULL = new Final(Value.NotNull);
public static final Final FINAL_NULL = new Final(Value.Null);
@@ -136,11 +138,6 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
List<Equation> equations = new ArrayList<>(argumentTypes.length * 4 + 3);
equations.add(PurityAnalysis.analyze(method, methodNode, stable));
if (argumentTypes.length == 0 && !isInterestingResult) {
// no need to continue analysis
return equations;
}
try {
final ControlFlowGraph graph = ControlFlowGraph.build(className, methodNode, jsr);
if (graph.transitions.length > 0) {
@@ -158,14 +155,14 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
RichControlFlow richControlFlow = new RichControlFlow(graph, dfs);
if (richControlFlow.reducible()) {
NegationAnalysis negated = tryNegation(method, argumentTypes, graph, isBooleanResult, dfs, jsr);
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, isReferenceResult, isBooleanResult, stable, jsr, equations, negated);
processBranchingMethod(method, methodNode, richControlFlow, argumentTypes, resultType, stable, jsr, equations, negated);
return equations;
}
LOG.debug(method + ": CFG is not reducible");
}
// simple
else {
processNonBranchingMethod(method, argumentTypes, graph, isReferenceResult, isBooleanResult, stable, equations);
processNonBranchingMethod(method, argumentTypes, graph, resultType, stable, equations);
return equations;
}
}
@@ -279,33 +276,22 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
final MethodNode methodNode,
final RichControlFlow richControlFlow,
Type[] argumentTypes,
boolean isReferenceResult,
boolean isBooleanResult,
Type resultType,
final boolean stable,
boolean jsr,
List<Equation> result,
NegationAnalysis negatedAnalysis) throws AnalyzerException {
final boolean isReferenceResult = ASMUtils.isReferenceType(resultType);
final boolean isBooleanResult = ASMUtils.isBooleanType(resultType);
boolean isInterestingResult = isBooleanResult || isReferenceResult;
boolean maybeLeakingParameter = isInterestingResult;
for (Type argType : argumentTypes) {
if (ASMUtils.isReferenceType(argType)) {
maybeLeakingParameter = true;
break;
}
}
final LeakingParameters leakingParametersAndFrames =
maybeLeakingParameter ? leakingParametersAndFrames(method, methodNode, argumentTypes, jsr) : null;
final LeakingParameters leakingParametersAndFrames = leakingParametersAndFrames(method, methodNode, argumentTypes, jsr);
boolean[] leakingParameters =
leakingParametersAndFrames != null ? leakingParametersAndFrames.parameters : null;
boolean[] leakingNullableParameters =
leakingParametersAndFrames != null ? leakingParametersAndFrames.nullableParameters : null;
boolean[] leakingParameters = leakingParametersAndFrames.parameters;
boolean[] leakingNullableParameters = leakingParametersAndFrames.nullableParameters;
final boolean[] origins =
isInterestingResult ?
OriginsAnalysis.resultOrigins(leakingParametersAndFrames.frames, methodNode.instructions, richControlFlow.controlFlow) :
null;
OriginsAnalysis.resultOrigins(leakingParametersAndFrames.frames, methodNode.instructions, richControlFlow.controlFlow);
Equation outEquation =
isInterestingResult ?
@@ -316,6 +302,18 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
result.add(outEquation);
result.add(new Equation(new Key(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);
} else {
final InThrowAnalysis inThrowAnalysis = new InThrowAnalysis(richControlFlow, Throw, origins, stable, sharedPendingStates);
throwEquation = inThrowAnalysis.analyze();
result.add(throwEquation);
shouldInferNonTrivialFailingContracts = !inThrowAnalysis.myHasNonTrivialReturn;
}
boolean withCycle = !richControlFlow.dfsTree.back.isEmpty();
if (argumentTypes.length > 50 && withCycle) {
@@ -323,6 +321,31 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
return;
}
final IntFunction<Function<Value, Stream<Equation>>> inOuts =
index -> val -> {
if (isBooleanResult && negatedAnalysis != null) {
return Stream.of(negatedAnalysis.contractEquation(index, val, stable));
}
Stream.Builder<Equation> builder = Stream.builder();
try {
if (isInterestingResult) {
builder.add(new InOutAnalysis(richControlFlow, new InOut(index, val), origins, stable, sharedPendingStates).analyze());
}
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));
}
else {
builder.add(new InThrowAnalysis(richControlFlow, direction, origins, stable, sharedPendingStates).analyze());
}
}
}
catch (AnalyzerException e) {
throw new RuntimeException("Analyzer error", e);
}
return builder.build();
};
// arguments and contract clauses
for (int i = 0; i < argumentTypes.length; i++) {
boolean notNullParam = false;
@@ -355,61 +378,46 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<Byte
}
if (isInterestingResult) {
if (leakingParameters[i]) {
if (notNullParam) {
// @NotNull, so "null->fail"
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
}
else {
// may be null on some branch, running "null->..." analysis
if (isBooleanResult && negatedAnalysis != null) {
result.add(negatedAnalysis.contractEquation(i, Value.Null, stable));
}
else {
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.Null), origins, stable, sharedPendingStates).analyze());
}
}
if (isBooleanResult && negatedAnalysis != null) {
result.add(negatedAnalysis.contractEquation(i, Value.NotNull, stable));
}
else {
result.add(new InOutAnalysis(richControlFlow, new InOut(i, Value.NotNull), origins, stable, sharedPendingStates).analyze());
}
}
else {
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));
continue;
}
if (notNullParam) {
// @NotNull, like "null->fail"
result.add(new Equation(new Key(method, new InOut(i, Value.Null), stable), FINAL_BOT));
continue;
}
}
}
Value.typeValues(argumentTypes[i]).flatMap(inOuts.apply(i)).forEach(result::add);
}
}
private void processNonBranchingMethod(Method method,
Type[] argumentTypes,
ControlFlowGraph graph,
boolean isReferenceResult,
boolean isBooleanResult,
Type returnType,
boolean stable,
List<Equation> result) throws AnalyzerException {
CombinedAnalysis analyzer = new CombinedAnalysis(method, graph);
analyzer.analyze();
if (isReferenceResult) {
result.add(analyzer.outContractEquation(stable));
ContainerUtil.addIfNotNull(result, analyzer.outContractEquation(stable));
ContainerUtil.addIfNotNull(result, analyzer.failEquation(stable));
if (ASMUtils.isReferenceType(returnType)) {
result.add(analyzer.nullableResultEquation(stable));
}
for (int i = 0; i < argumentTypes.length; i++) {
Type argType = argumentTypes[i];
EntryStream.of(argumentTypes).forKeyValue((i, argType) -> {
if (ASMUtils.isReferenceType(argType)) {
result.add(analyzer.notNullParamEquation(i, stable));
result.add(analyzer.nullableParamEquation(i, stable));
if (isReferenceResult || isBooleanResult) {
result.add(analyzer.contractEquation(i, Value.Null, stable));
result.add(analyzer.contractEquation(i, Value.NotNull, stable));
}
}
}
Value.typeValues(argType)
.flatMap(val -> Stream.of(analyzer.contractEquation(i, val, stable), analyzer.failEquation(i, val, stable)))
.filter(Objects::nonNull)
.forEach(result::add);
});
}
private List<Equation> topEquations(Method method,

View File

@@ -20,6 +20,7 @@ import com.intellij.codeInspection.bytecodeAnalysis.asm.ControlFlowGraph;
import com.intellij.util.SingletonSet;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.org.objectweb.asm.Handle;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.tree.*;
@@ -28,9 +29,11 @@ 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.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*;
import static com.intellij.codeInspection.bytecodeAnalysis.CombinedData.*;
@@ -99,14 +102,20 @@ interface CombinedData {
public int getOriginInsnIndex() {
return originInsnIndex;
}
}
final class NthParamValue extends BasicValue {
final int n;
public NthParamValue(Type type, int n) {
super(type);
this.n = n;
@NotNull
Set<Key> getKeysForParameter(int idx, ParamValueBasedDirection direction) {
Set<Key> 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));
}
}
}
return keys;
}
}
@@ -237,8 +246,10 @@ final class CombinedAnalysis {
return new Equation(key, result);
}
@Nullable
final Equation contractEquation(int i, Value inValue, boolean stable) {
final Key key = new Key(method, new InOut(i, inValue), stable);
final InOut direction = new InOut(i, inValue);
final Key key = new Key(method, direction, stable);
final Result result;
if (exception || (inValue == Value.Null && interpreter.dereferencedParams[i])) {
result = new Final(Value.Bot);
@@ -260,31 +271,63 @@ final class CombinedAnalysis {
}
else if (returnValue instanceof TrackableCallValue) {
TrackableCallValue call = (TrackableCallValue)returnValue;
HashSet<Key> keys = new HashSet<>();
for (int argI = 0; argI < call.args.size(); argI++) {
BasicValue arg = call.args.get(argI);
if (arg instanceof NthParamValue) {
NthParamValue npv = (NthParamValue)arg;
if (npv.n == i) {
keys.add(new Key(call.method, new InOut(argI, inValue), call.stableCall));
}
}
}
Set<Key> keys = call.getKeysForParameter(i, direction);
if (ASMUtils.isReferenceType(call.getType())) {
keys.add(new Key(call.method, Out, call.stableCall));
}
if (keys.isEmpty()) {
result = new Final(Value.Top);
return null;
} else {
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
}
}
else {
result = new Final(Value.Top);
return null;
}
return new Equation(key, result);
}
@Nullable
final Equation failEquation(boolean stable) {
final Key key = new Key(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)));
}
else {
return null;
}
return new Equation(key, result);
}
@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 Result result;
if (exception) {
result = new Final(Value.Fail);
}
else if (!interpreter.calls.isEmpty()) {
Set<Key> keys = new HashSet<>();
for (TrackableCallValue call : interpreter.calls) {
keys.addAll(call.getKeysForParameter(i, direction));
keys.add(new Key(call.method, Throw, call.stableCall));
}
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
}
else {
return null;
}
return new Equation(key, result);
}
@Nullable
final Equation outContractEquation(boolean stable) {
final Key key = new Key(method, Out, stable);
final Result result;
@@ -310,7 +353,7 @@ final class CombinedAnalysis {
result = new Pending(new SingletonSet<>(new Product(Value.Top, keys)));
}
else {
result = new Final(Value.Top);
return null;
}
return new Equation(key, result);
}
@@ -376,6 +419,9 @@ final class CombinedInterpreter extends BasicInterpreter {
// Trackable values that were dereferenced during execution of a method
// Values are are identified by `origin` index
final boolean[] dereferencedValues;
final List<TrackableCallValue> calls = new ArrayList<>();
private final InsnList insns;
CombinedInterpreter(InsnList insns, int arity) {
@@ -536,7 +582,9 @@ final class CombinedInterpreter extends BasicInterpreter {
case INVOKEINTERFACE: {
MethodInsnNode mNode = (MethodInsnNode)insn;
Method method = new Method(mNode.owner, mNode.name, mNode.desc);
return methodCall(opCode, origin, method, values);
TrackableCallValue value = methodCall(opCode, origin, method, values);
calls.add(value);
return value;
}
case INVOKEDYNAMIC: {
LambdaIndy lambda = LambdaIndy.from((InvokeDynamicInsnNode)insn);
@@ -554,7 +602,7 @@ final class CombinedInterpreter extends BasicInterpreter {
}
@NotNull
private BasicValue methodCall(int opCode, int origin, Method method, List<? extends BasicValue> values) {
private TrackableCallValue methodCall(int opCode, int origin, Method method, List<? extends BasicValue> values) {
Type retType = Type.getReturnType(method.methodDesc);
boolean stable = opCode == INVOKESTATIC || opCode == INVOKESPECIAL;
boolean thisCall = false;

View File

@@ -15,6 +15,7 @@
*/
package com.intellij.codeInspection.bytecodeAnalysis;
import com.intellij.codeInspection.bytecodeAnalysis.Direction.ParamValueBasedDirection;
import com.intellij.codeInspection.bytecodeAnalysis.asm.ASMUtils;
import com.intellij.codeInspection.bytecodeAnalysis.asm.ControlFlowGraph.Edge;
import com.intellij.codeInspection.bytecodeAnalysis.asm.RichControlFlow;
@@ -34,28 +35,27 @@ import java.util.List;
import java.util.Set;
import static com.intellij.codeInspection.bytecodeAnalysis.AbstractValues.*;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.InOut;
import static com.intellij.codeInspection.bytecodeAnalysis.Direction.Out;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
class InOutAnalysis extends Analysis<Result> {
abstract class ContractAnalysis extends Analysis<Result> {
static final ResultUtil resultUtil =
new ResultUtil(new ELattice<>(Value.Bot, Value.Top));
final private State[] pending;
private final InOutInterpreter interpreter;
private final Value inValue;
final InOutInterpreter interpreter;
final Value inValue;
private final int generalizeShift;
private Result internalResult;
Result internalResult;
private int id;
private int pendingTop;
protected InOutAnalysis(RichControlFlow richControlFlow, Direction direction, boolean[] resultOrigins, boolean stable, State[] pending) {
protected ContractAnalysis(RichControlFlow richControlFlow, Direction direction, boolean[] resultOrigins, boolean stable, State[] pending) {
super(richControlFlow, direction, stable);
this.pending = pending;
interpreter = new InOutInterpreter(direction, richControlFlow.controlFlow.methodNode.instructions, resultOrigins);
inValue = direction instanceof InOut ? ((InOut)direction).inValue : null;
inValue = direction instanceof ParamValueBasedDirection ? ((ParamValueBasedDirection)direction).inValue : null;
generalizeShift = (methodNode.access & ACC_STATIC) == 0 ? 1 : 0;
internalResult = new Final(Value.Bot);
}
@@ -131,52 +131,8 @@ class InOutAnalysis extends Analysis<Result> {
addComputed(insnIndex, state);
if (interpreter.deReferenced) {
return;
}
int opcode = insnNode.getOpcode();
switch (opcode) {
case ARETURN:
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case RETURN:
BasicValue stackTop = popValue(frame);
Result subResult;
if (FalseValue == stackTop) {
subResult = new Final(Value.False);
}
else if (TrueValue == stackTop) {
subResult = new Final(Value.True);
}
else if (NullValue == stackTop) {
subResult = new Final(Value.Null);
}
else if (stackTop instanceof NotNullValue) {
subResult = new Final(Value.NotNull);
}
else if (stackTop instanceof ParamValue) {
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)));
}
else {
earlyResult = new Final(Value.Top);
return;
}
internalResult = resultUtil.join(internalResult, subResult);
if (internalResult instanceof Final && ((Final)internalResult).value == Value.Top) {
earlyResult = internalResult;
}
return;
case ATHROW:
return;
default:
}
if (handleReturn(frame, opcode)) return;
if (opcode == IFNONNULL && popValue(frame) instanceof ParamValue) {
int nextInsnIndex = inValue == Value.Null ? insnIndex + 1 : methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label);
@@ -192,6 +148,20 @@ class InOutAnalysis extends Analysis<Result> {
return;
}
if (opcode == IFEQ && popValue(frame) instanceof ParamValue) {
int nextInsnIndex = inValue == Value.True ? insnIndex + 1 : methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label);
State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false);
pendingPush(nextState);
return;
}
if (opcode == IFNE && popValue(frame) instanceof ParamValue) {
int nextInsnIndex = inValue == Value.False ? insnIndex + 1 : methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label);
State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false);
pendingPush(nextState);
return;
}
if (opcode == IFEQ && popValue(frame) == InstanceOfCheckValue && inValue == Value.Null) {
int nextInsnIndex = methodNode.instructions.indexOf(((JumpInsnNode)insnNode).label);
State nextState = new State(++id, new Conf(nextInsnIndex, nextFrame), nextHistory, true, false);
@@ -218,6 +188,8 @@ class InOutAnalysis extends Analysis<Result> {
}
}
abstract boolean handleReturn(Frame<BasicValue> frame, int opcode) throws AnalyzerException;
private void pendingPush(State st) throws AnalyzerException {
if (pendingTop >= STEPS_LIMIT) {
throw new AnalyzerException(null, "limit is reached in method " + method);
@@ -268,8 +240,117 @@ class InOutAnalysis extends Analysis<Result> {
}
}
class InOutAnalysis extends ContractAnalysis {
protected InOutAnalysis(RichControlFlow richControlFlow,
Direction direction,
boolean[] resultOrigins,
boolean stable,
State[] pending) {
super(richControlFlow, direction, resultOrigins, stable, pending);
}
boolean handleReturn(Frame<BasicValue> frame, int opcode) throws AnalyzerException {
if (interpreter.deReferenced) {
return true;
}
switch (opcode) {
case ARETURN:
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case RETURN:
BasicValue stackTop = popValue(frame);
Result subResult;
if (FalseValue == stackTop) {
subResult = new Final(Value.False);
}
else if (TrueValue == stackTop) {
subResult = new Final(Value.True);
}
else if (NullValue == stackTop) {
subResult = new Final(Value.Null);
}
else if (stackTop instanceof NotNullValue) {
subResult = new Final(Value.NotNull);
}
else if (stackTop instanceof ParamValue) {
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)));
}
else {
earlyResult = new Final(Value.Top);
return true;
}
internalResult = resultUtil.join(internalResult, subResult);
if (internalResult instanceof Final && ((Final)internalResult).value == Value.Top) {
earlyResult = internalResult;
}
return true;
case ATHROW:
return true;
default:
}
return false;
}
}
class InThrowAnalysis extends ContractAnalysis {
private BasicValue myReturnValue;
boolean myHasNonTrivialReturn;
protected InThrowAnalysis(RichControlFlow richControlFlow,
Direction direction,
boolean[] resultOrigins,
boolean stable,
State[] pending) {
super(richControlFlow, direction, resultOrigins, stable, pending);
}
boolean handleReturn(Frame<BasicValue> frame, int opcode) throws AnalyzerException {
Result subResult;
if (interpreter.deReferenced) {
subResult = new Final(Value.Top);
} else {
switch (opcode) {
case ARETURN:
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
BasicValue value = frame.pop();
if(!(value instanceof NthParamValue) && value != NullValue && value != TrueValue && value != FalseValue ||
myReturnValue != null && !myReturnValue.equals(value)) {
myHasNonTrivialReturn = true;
} else {
myReturnValue = value;
}
subResult = new Final(Value.Top);
break;
case RETURN:
subResult = new Final(Value.Top);
break;
case ATHROW:
subResult = new Final(Value.Fail);
break;
default:
return false;
}
}
internalResult = resultUtil.join(internalResult, subResult);
if (internalResult instanceof Final && ((Final)internalResult).value == Value.Top && myHasNonTrivialReturn) {
earlyResult = internalResult;
}
return true;
}
}
class InOutInterpreter extends BasicInterpreter {
final Direction direction;
final ParamValueBasedDirection direction;
final InsnList insns;
final boolean[] resultOrigins;
final boolean nullAnalysis;
@@ -277,10 +358,15 @@ class InOutInterpreter extends BasicInterpreter {
boolean deReferenced;
InOutInterpreter(Direction direction, InsnList insns, boolean[] resultOrigins) {
this.direction = direction;
this.insns = insns;
this.resultOrigins = resultOrigins;
nullAnalysis = (direction instanceof InOut) && (((InOut)direction).inValue) == Value.Null;
if(direction instanceof ParamValueBasedDirection) {
this.direction = (ParamValueBasedDirection)direction;
this.nullAnalysis = this.direction.inValue == Value.Null;
} else {
this.direction = null;
this.nullAnalysis = false;
}
}
@Override
@@ -420,12 +506,11 @@ class InOutInterpreter extends BasicInterpreter {
Type retType = Type.getReturnType(mNode.desc);
boolean isRefRetType = retType.getSort() == Type.OBJECT || retType.getSort() == Type.ARRAY;
if (!Type.VOID_TYPE.equals(retType)) {
if (direction instanceof InOut) {
InOut inOut = (InOut)direction;
if (direction != null) {
HashSet<Key> keys = new HashSet<>();
for (int i = shift; i < values.size(); i++) {
if (values.get(i) instanceof ParamValue) {
keys.add(new Key(method, new InOut(i - shift, inOut.inValue), stable));
keys.add(new Key(method, direction.withIndex(i - shift), stable));
}
}
if (isRefRetType) {

View File

@@ -15,10 +15,107 @@
*/
package com.intellij.codeInspection.bytecodeAnalysis;
public interface Direction {
final class In implements Direction {
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
public abstract class Direction {
public static final Direction Out = explicitDirection("Out");
public static final Direction NullableOut = explicitDirection("NullableOut");
public static final Direction Pure = explicitDirection("Pure");
public static final Direction Throw = explicitDirection("Throw");
private static final List<Direction> ourConcreteDirections = Arrays.asList(Out, NullableOut, Pure, Throw);
private static final int CONCRETE_DIRECTIONS_OFFSET = ourConcreteDirections.size();
private static final int IN_OUT_OFFSET = 2; // nullity mask is 0/1
private static final int IN_THROW_OFFSET = 2 + Value.values().length;
private static final int DIRECTIONS_PER_PARAM_ID = IN_THROW_OFFSET + Value.values().length;
/**
* Converts int to Direction object.
*
* @param directionKey int representation of direction
* @return Direction object
* @see #asInt()
*/
@NotNull
static Direction fromInt(int directionKey) {
if(directionKey < CONCRETE_DIRECTIONS_OFFSET) {
return ourConcreteDirections.get(directionKey);
}
int paramKey = directionKey - CONCRETE_DIRECTIONS_OFFSET;
int paramId = paramKey / DIRECTIONS_PER_PARAM_ID;
int subDirectionId = paramKey % DIRECTIONS_PER_PARAM_ID;
// 0 - 1 - @NotNull, @Nullable, parameter
if (subDirectionId < IN_OUT_OFFSET) {
return new In(paramId, subDirectionId);
}
if (subDirectionId < IN_THROW_OFFSET) {
int valueId = subDirectionId - IN_OUT_OFFSET;
return new InOut(paramId, Value.values()[valueId]);
}
int valueId = subDirectionId - IN_THROW_OFFSET;
return new InThrow(paramId, Value.values()[valueId]);
}
/**
* Encodes Direction object as int.
*
* @return unique int for direction
*/
abstract int asInt();
@Override
public int hashCode() {
return asInt();
}
@Override
public boolean equals(Object obj) {
if(obj == this) return true;
if(obj == null || obj.getClass() != this.getClass()) return false;
return asInt() == ((Direction)obj).asInt();
}
@NotNull
private static Direction explicitDirection(String name) {
return new Direction() {
private String myName = name;
@Override
int asInt() {
for (int i = 0; i < ourConcreteDirections.size(); i++) {
if(ourConcreteDirections.get(i) == this) return i;
}
throw new InternalError("Explicit direction absent in ourConcreteDirections: "+myName);
}
@Override
public String toString() {
return myName;
}
};
}
abstract static class ParamIdBasedDirection extends Direction {
final int paramIndex;
protected ParamIdBasedDirection(int index) {
paramIndex = index;
}
public int paramId() {
return paramIndex;
}
@Override
int asInt() {
return CONCRETE_DIRECTIONS_OFFSET + DIRECTIONS_PER_PARAM_ID * this.paramId();
}
}
static final class In extends ParamIdBasedDirection {
static final int NOT_NULL_MASK = 0;
static final int NULLABLE_MASK = 1;
/**
@@ -28,111 +125,72 @@ public interface Direction {
final int nullityMask;
In(int paramIndex, int nullityMask) {
this.paramIndex = paramIndex;
super(paramIndex);
this.nullityMask = nullityMask;
}
@Override
int asInt() {
return super.asInt() + nullityMask;
}
@Override
public String toString() {
return "In " + paramIndex;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
In in = (In)o;
if (paramIndex != in.paramIndex) return false;
if (nullityMask != in.nullityMask) return false;
return true;
}
@Override
public int hashCode() {
return 31 * paramIndex + nullityMask;
}
public int paramId() {
return paramIndex;
}
}
final class InOut implements Direction {
final int paramIndex;
static abstract class ParamValueBasedDirection extends ParamIdBasedDirection {
final Value inValue;
InOut(int paramIndex, Value inValue) {
this.paramIndex = paramIndex;
ParamValueBasedDirection(int paramIndex, Value inValue) {
super(paramIndex);
this.inValue = inValue;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
abstract ParamValueBasedDirection withIndex(int paramIndex);
}
InOut inOut = (InOut)o;
if (paramIndex != inOut.paramIndex) return false;
if (inValue != inOut.inValue) return false;
return true;
static final class InOut extends ParamValueBasedDirection {
InOut(int paramIndex, Value inValue) {
super(paramIndex, inValue);
}
@Override
public int hashCode() {
int result = paramIndex;
result = 31 * result + inValue.ordinal();
return result;
InOut withIndex(int paramIndex) {
return new InOut(paramIndex, inValue);
}
@Override
int asInt() {
return super.asInt() + IN_OUT_OFFSET + inValue.ordinal();
}
@Override
public String toString() {
return "InOut " + paramIndex + " " + inValue.toString();
}
public int paramId() {
return paramIndex;
}
public int valueId() {
return inValue.ordinal();
}
}
Direction Out = new Direction() {
@Override
public String toString() {
return "Out";
static final class InThrow extends ParamValueBasedDirection {
InThrow(int paramIndex, Value inValue) {
super(paramIndex, inValue);
}
@Override
public int hashCode() {
return -1;
}
};
Direction NullableOut = new Direction() {
@Override
public String toString() {
return "NullableOut";
InThrow withIndex(int paramIndex) {
return new InThrow(paramIndex, inValue);
}
@Override
public int hashCode() {
return -2;
}
};
Direction Pure = new Direction() {
@Override
public int hashCode() {
return -3;
int asInt() {
return super.asInt() + IN_THROW_OFFSET + inValue.ordinal();
}
@Override
public String toString() {
return "Pure";
return "InThrow " + paramIndex + " " + inValue.toString();
}
};
}
}

View File

@@ -15,6 +15,7 @@
*/
package com.intellij.codeInspection.bytecodeAnalysis;
import one.util.streamex.IntStreamEx;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -75,11 +76,24 @@ public final class HKey {
return dirKey == 0 ? this : new HKey(key, 0, stable, false);
}
HKey updateDirection(int newDirKey) {
return new HKey(key, newDirKey, stable, false);
HKey withDirection(Direction dir) {
return new HKey(key, dir.asInt(), stable, false);
}
HKey negate() {
return new HKey(key, dirKey, stable, true);
}
public Direction getDirection() {
return Direction.fromInt(dirKey);
}
@Override
public String toString() {
return "HKey [" + bytesToString(key) + "|" + (stable ? "S" : "-") + (negated ? "N" : "-") + "|" + getDirection() + "]";
}
static String bytesToString(byte[] key) {
return IntStreamEx.of(key).mapToObj(b -> String.format("%02x", b & 0xFF)).joining(".");
}
}

View File

@@ -27,10 +27,6 @@ import java.util.function.Function;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
enum Value {
Bot, NotNull, Null, True, False, Pure, Top
}
final class LambdaIndy {
private static final String LAMBDA_METAFACTORY_CLASS = "java/lang/invoke/LambdaMetafactory";
private static final String LAMBDA_METAFACTORY_METHOD = "metafactory";

View File

@@ -36,6 +36,7 @@ import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.ConcurrentFactoryMap;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -286,22 +287,30 @@ public class ProjectBytecodeAnalysis {
throws EquationsLimitException {
MethodAnnotations result = new MethodAnnotations();
final Solver outSolver = new Solver(new ELattice<>(Value.Bot, Value.Top), Value.Top);
final PuritySolver puritySolver = new PuritySolver();
collectEquations(allKeys, outSolver);
collectPurityEquations(key.updateDirection(BytecodeAnalysisConverter.mkDirectionKey(Pure)), puritySolver);
collectPurityEquations(key.withDirection(Pure), puritySolver);
Map<HKey, Value> solutions = outSolver.solve();
Map<HKey, Set<HEffectQuantum>> puritySolutions = puritySolver.solve();
int arity = owner.getParameterList().getParameters().length;
BytecodeAnalysisConverter.addMethodAnnotations(solutions, result, key, arity);
BytecodeAnalysisConverter.addEffectAnnotations(puritySolutions, result, key, owner.isConstructor());
HKey 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) {
// Always failing method
result.contractsValues.put(key, StreamEx.constant("_", arity).joining(",", "\"", "->fail\""));
} else {
final Solver outSolver = new Solver(new ELattice<>(Value.Bot, Value.Top), Value.Top);
collectEquations(allKeys, outSolver);
Map<HKey, 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.updateDirection(BytecodeAnalysisConverter.mkDirectionKey(NullableOut));
HKey nullableKey = key.withDirection(NullableOut);
if (nullableMethodTransitivity) {
collectEquations(Collections.singletonList(nullableKey), nullableMethodSolver);
}

View File

@@ -243,6 +243,11 @@ final class CoreHKey {
result = 31 * result + dirKey;
return result;
}
@Override
public String toString() {
return "CoreHKey [" + HKey.bytesToString(key) + "|" + Direction.fromInt(dirKey) + "]";
}
}
final class Solver {

View File

@@ -0,0 +1,47 @@
/*
* 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 com.intellij.codeInspection.bytecodeAnalysis.asm.ASMUtils;
import com.intellij.codeInspection.dataFlow.MethodContract;
import org.jetbrains.org.objectweb.asm.Type;
import java.util.stream.Stream;
enum Value {
Bot, NotNull, Null, True, False, Fail, Pure, Top;
static Stream<Value> typeValues(Type type) {
if(ASMUtils.isReferenceType(type)) {
return Stream.of(Null, NotNull);
} else if(ASMUtils.isBooleanType(type)) {
return Stream.of(True, False);
}
return Stream.empty();
}
MethodContract.ValueConstraint toValueConstraint() {
switch (this) {
case False: return MethodContract.ValueConstraint.FALSE_VALUE;
case True: return MethodContract.ValueConstraint.TRUE_VALUE;
case NotNull: return MethodContract.ValueConstraint.NOT_NULL_VALUE;
case Null: return MethodContract.ValueConstraint.NULL_VALUE;
case Fail: return MethodContract.ValueConstraint.THROW_EXCEPTION;
default: return MethodContract.ValueConstraint.ANY_VALUE;
}
}
}

View File

@@ -65,7 +65,7 @@ public abstract class MethodContract {
/**
* @return true if this contract result does not depend on arguments
*/
boolean isTrivial() {
public boolean isTrivial() {
return getConditions().isEmpty();
}

View File

@@ -175,9 +175,7 @@
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.awt.Component java.awt.Dimension getSize(java.awt.Dimension)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null;null-&gt;!null&quot;"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.awt.Component java.awt.Dimension getSize(java.awt.Dimension) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>

View File

@@ -652,6 +652,7 @@
</item>
<item name='java.lang.ClassLoader java.lang.Class findClass(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -666,7 +667,7 @@
</item>
<item name='java.lang.ClassLoader java.lang.String findLibrary(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;null;null-&gt;null&quot;"/>
<val name="value" val="&quot;_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -679,7 +680,7 @@
</item>
<item name='java.lang.ClassLoader java.net.URL findResource(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;null;null-&gt;null&quot;"/>
<val name="value" val="&quot;_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -847,6 +848,7 @@
</item>
<item name='java.lang.Enum java.lang.Object clone()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2379,6 +2381,7 @@
</item>
<item name='java.lang.System void checkKey(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;null-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2426,6 +2429,7 @@
</item>
<item name='java.lang.ThreadLocal T childValue(T)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -7,7 +7,7 @@
</item>
<item name='java.lang.invoke.AdapterMethodHandle boolean canBoxArgument(java.lang.Class&lt;?&gt;, java.lang.Class&lt;?&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null,_-&gt;false;_,!null-&gt;false;_,null-&gt;false;null,_-&gt;false&quot;"/>
<val name="value" val="&quot;_,_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -132,8 +132,7 @@
</item>
<item name='java.lang.invoke.AdapterMethodHandle java.lang.invoke.MethodHandle makeBoxArgument(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle, int, java.lang.Class&lt;?&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value"
val="&quot;!null,_,_,_-&gt;null;_,!null,_,_-&gt;null;_,_,_,!null-&gt;null;_,_,_,null-&gt;null;_,null,_,_-&gt;null;null,_,_,_-&gt;null&quot;"/>
<val name="value" val="&quot;_,_,_,_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -409,6 +408,7 @@
</item>
<item name='java.lang.invoke.ConstantCallSite void setTarget(java.lang.invoke.MethodHandle)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -440,6 +440,7 @@
</item>
<item name='java.lang.invoke.FilterGeneric java.lang.invoke.FilterGeneric.Adapter buildAdapterFromBytecodes(java.lang.invoke.MethodType, java.lang.invoke.FilterGeneric.Kind, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -807,6 +808,7 @@
</item>
<item name='java.lang.invoke.FromGeneric java.lang.invoke.FromGeneric.Adapter buildAdapterFromBytecodes(java.lang.invoke.MethodType)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -986,6 +988,11 @@
<item name='java.lang.invoke.FromGeneric.Adapter java.lang.Class&lt;? extends java.lang.invoke.FromGeneric.Adapter&gt; findSubClass(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='java.lang.invoke.InvokeDynamic InvokeDynamic()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;-&gt;fail&quot;"/>
</annotation>
</item>
<item name='java.lang.invoke.InvokeGeneric boolean returnConversionNeeded(java.lang.invoke.MethodType, java.lang.invoke.MethodHandle)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;_,null-&gt;false&quot;"/>
@@ -1002,6 +1009,7 @@
</item>
<item name='java.lang.invoke.InvokeGeneric java.lang.invoke.MethodHandle addReturnConversion(java.lang.invoke.MethodHandle, java.lang.Class&lt;?&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1586,6 +1594,11 @@
<item name='java.lang.invoke.MethodHandleNatives void notifyGenericMethodType(java.lang.invoke.MethodType) 0'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.lang.invoke.MethodHandleNatives void raiseException(int, java.lang.Object, java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;_,_,_-&gt;fail&quot;"/>
</annotation>
</item>
<item name='java.lang.invoke.MethodHandleNatives void raiseException(int, java.lang.Object, java.lang.Object) 1'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -1990,7 +2003,7 @@
</item>
<item name='java.lang.invoke.MethodHandles.Lookup java.lang.invoke.MethodHandle makeAccessor(java.lang.Class&lt;?&gt;, java.lang.invoke.MemberName, boolean, boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;_,null,_,_-&gt;!null&quot;"/>
<val val="&quot;_,_,true,_-&gt;!null;_,null,_,_-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.lang.invoke.MethodHandles.Lookup java.lang.invoke.MethodHandle makeAccessor(java.lang.Class&lt;?&gt;, java.lang.invoke.MemberName, boolean, boolean) 1'>
@@ -2332,6 +2345,7 @@
</item>
<item name='java.lang.invoke.SpreadGeneric java.lang.invoke.SpreadGeneric.Adapter buildAdapterFromBytecodes(java.lang.invoke.MethodType, int, java.lang.invoke.MethodHandle[])'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2510,6 +2524,7 @@
</item>
<item name='java.lang.invoke.ToGeneric java.lang.invoke.ToGeneric.Adapter buildAdapterFromBytecodes(java.lang.invoke.MethodType)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -119,7 +119,7 @@
</item>
<item name='java.net.InetAddress boolean equals(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -826,7 +826,7 @@
</item>
<item name='java.net.URLConnection java.lang.String getDefaultRequestProperty(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;null;null-&gt;null&quot;"/>
<val name="value" val="&quot;_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -842,7 +842,7 @@
</item>
<item name='java.net.URLConnection java.lang.String getHeaderField(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;null;null-&gt;null&quot;"/>
<val name="value" val="&quot;_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>

View File

@@ -51,6 +51,11 @@
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.security.Identity java.lang.String toString(boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;true-&gt;!null&quot;"/>
</annotation>
</item>
<item name='java.security.NoSuchAlgorithmException NoSuchAlgorithmException()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>

View File

@@ -9,6 +9,7 @@
</item>
<item name='java.util.AbstractCollection boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -42,6 +43,7 @@
</item>
<item name='java.util.AbstractList E set(int, E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -50,7 +52,7 @@
</item>
<item name='java.util.AbstractList boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val val="&quot;_-&gt;true&quot;"/>
</annotation>
</item>
<item name='java.util.AbstractList boolean addAll(int, java.util.Collection&lt;? extends E&gt;) 1'>
@@ -82,6 +84,7 @@
</item>
<item name='java.util.AbstractList void add(int, E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -131,6 +134,7 @@
</item>
<item name='java.util.AbstractMap V put(K, V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -203,7 +207,7 @@
</item>
<item name='java.util.AbstractQueue boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val val="&quot;_-&gt;true&quot;"/>
</annotation>
</item>
<item name='java.util.AbstractQueue boolean addAll(java.util.Collection&lt;? extends E&gt;) 0'>
@@ -251,7 +255,7 @@
</item>
<item name='java.util.ArrayList boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val val="&quot;_-&gt;true&quot;"/>
</annotation>
</item>
<item name='java.util.ArrayList boolean addAll(int, java.util.Collection&lt;? extends E&gt;) 1'>
@@ -1322,7 +1326,7 @@
</item>
<item name='java.util.Collections.AsLIFOQueue boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val val="&quot;_-&gt;true&quot;"/>
</annotation>
</item>
<item name='java.util.Collections.CheckedCollection CheckedCollection(java.util.Collection&lt;E&gt;, java.lang.Class&lt;E&gt;)'>
@@ -1422,6 +1426,7 @@
</item>
<item name='java.util.Collections.CheckedMap.CheckedEntrySet boolean add(java.util.Map.Entry&lt;K,V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1430,6 +1435,7 @@
</item>
<item name='java.util.Collections.CheckedMap.CheckedEntrySet boolean addAll(java.util.Collection&lt;? extends java.util.Map.Entry&lt;K,V&gt;&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1640,7 +1646,7 @@
</item>
<item name='java.util.Collections.EmptyList boolean contains(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1721,6 +1727,7 @@
</item>
<item name='java.util.Collections.EmptyListIterator void add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1729,6 +1736,7 @@
</item>
<item name='java.util.Collections.EmptyListIterator void set(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1742,7 +1750,7 @@
</item>
<item name='java.util.Collections.EmptyMap V get(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;null;null-&gt;null&quot;"/>
<val name="value" val="&quot;_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -1752,7 +1760,7 @@
</item>
<item name='java.util.Collections.EmptyMap boolean containsKey(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1761,7 +1769,7 @@
</item>
<item name='java.util.Collections.EmptyMap boolean containsValue(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1826,7 +1834,7 @@
</item>
<item name='java.util.Collections.EmptySet boolean contains(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2102,6 +2110,7 @@
</item>
<item name='java.util.Collections.UnmodifiableCollection boolean add(E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2110,6 +2119,7 @@
</item>
<item name='java.util.Collections.UnmodifiableCollection boolean addAll(java.util.Collection&lt;? extends E&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2118,6 +2128,7 @@
</item>
<item name='java.util.Collections.UnmodifiableCollection boolean remove(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2126,6 +2137,7 @@
</item>
<item name='java.util.Collections.UnmodifiableCollection boolean removeAll(java.util.Collection&lt;?&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2134,6 +2146,7 @@
</item>
<item name='java.util.Collections.UnmodifiableCollection boolean retainAll(java.util.Collection&lt;?&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2155,6 +2168,7 @@
</item>
<item name='java.util.Collections.UnmodifiableList E set(int, E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2171,6 +2185,7 @@
</item>
<item name='java.util.Collections.UnmodifiableList boolean addAll(int, java.util.Collection&lt;? extends E&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2191,6 +2206,7 @@
</item>
<item name='java.util.Collections.UnmodifiableList void add(int, E)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2207,6 +2223,7 @@
</item>
<item name='java.util.Collections.UnmodifiableMap V put(K, V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2218,6 +2235,7 @@
</item>
<item name='java.util.Collections.UnmodifiableMap V remove(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2231,6 +2249,7 @@
</item>
<item name='java.util.Collections.UnmodifiableMap void putAll(java.util.Map&lt;? extends K,? extends V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2272,6 +2291,7 @@
</item>
<item name='java.util.Collections.UnmodifiableMap.UnmodifiableEntrySet.UnmodifiableEntry V setValue(V)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -2996,7 +3016,7 @@
</item>
<item name='java.util.LinkedHashMap boolean removeEldestEntry(java.util.Map.Entry&lt;K,V&gt;)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -235,9 +235,7 @@
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='javax.swing.JComponent java.awt.Dimension getSize(java.awt.Dimension)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null;null-&gt;!null&quot;"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='javax.swing.JComponent java.awt.Dimension getSize(java.awt.Dimension) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -592,6 +590,7 @@
</item>
<item name='javax.swing.SwingUtilities SwingUtilities()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -46,6 +46,7 @@
</item>
<item name='org.apache.commons.collections.iterators.AbstractEmptyIterator java.lang.Object setValue(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -54,6 +55,7 @@
</item>
<item name='org.apache.commons.collections.iterators.AbstractEmptyIterator void add(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -72,6 +74,7 @@
</item>
<item name='org.apache.commons.collections.iterators.AbstractEmptyIterator void set(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -400,7 +400,7 @@
</item>
<item name='org.apache.commons.collections.map.LRUMap boolean removeLRU(org.apache.commons.collections.map.AbstractLinkedMap.LinkEntry)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val name="value" val="&quot;_-&gt;true&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -3147,9 +3147,9 @@
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String defaultString(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;!null;null-&gt;!null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.StringUtils java.lang.String defaultString(java.lang.String) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -4070,16 +4070,19 @@
</item>
<item name='org.apache.commons.lang.Validate void isTrue(boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;false-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.lang.Validate void isTrue(boolean, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;false,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.lang.Validate void isTrue(boolean, java.lang.String, double)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;false,_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4088,6 +4091,7 @@
</item>
<item name='org.apache.commons.lang.Validate void isTrue(boolean, java.lang.String, java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;false,_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4099,6 +4103,7 @@
</item>
<item name='org.apache.commons.lang.Validate void isTrue(boolean, java.lang.String, long)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;false,_,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4129,6 +4134,7 @@
</item>
<item name='org.apache.commons.lang.Validate void notEmpty(java.lang.Object[])'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;null-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4137,6 +4143,7 @@
</item>
<item name='org.apache.commons.lang.Validate void notEmpty(java.lang.Object[], java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;null,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4145,6 +4152,7 @@
</item>
<item name='org.apache.commons.lang.Validate void notEmpty(java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;null-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4153,6 +4161,7 @@
</item>
<item name='org.apache.commons.lang.Validate void notEmpty(java.lang.String, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;null,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4173,6 +4182,7 @@
</item>
<item name='org.apache.commons.lang.Validate void notNull(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;null-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -4181,6 +4191,7 @@
</item>
<item name='org.apache.commons.lang.Validate void notNull(java.lang.Object, java.lang.String)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;null,_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -280,7 +280,7 @@
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction addSub(org.apache.commons.lang.math.Fraction, boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;!null&quot;"/>
<val val="&quot;!null,_-&gt;!null;_,false-&gt;!null&quot;"/>
</annotation>
</item>
<item name='org.apache.commons.lang.math.Fraction org.apache.commons.lang.math.Fraction addSub(org.apache.commons.lang.math.Fraction, boolean) 0'>
@@ -472,11 +472,13 @@
</item>
<item name='org.apache.commons.lang.math.JVMRandom double nextGaussian()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='org.apache.commons.lang.math.JVMRandom void nextBytes(byte[])'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -76,9 +76,7 @@
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
<item name='org.apache.commons.lang.text.StrBuilder char[] getChars(char[])'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null-&gt;!null;null-&gt;!null&quot;"/>
</annotation>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='org.apache.commons.lang.text.StrBuilder char[] getChars(char[]) 0'>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -602,6 +600,7 @@
</item>
<item name='org.apache.commons.lang.text.StrMatcher.NoMatcher int isMatch(char[], int, int, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_,_,_,_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -942,6 +941,7 @@
</item>
<item name='org.apache.commons.lang.text.StrTokenizer void add(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -955,6 +955,7 @@
</item>
<item name='org.apache.commons.lang.text.StrTokenizer void set(java.lang.Object)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -18,12 +18,12 @@
</item>
<item name='org.apache.velocity.app.Velocity boolean mergeTemplate(java.lang.String, java.lang.String, org.apache.velocity.context.Context, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_-&gt;true;_,!null,_,_-&gt;true;_,_,!null,_-&gt;true;_,_,_,!null-&gt;true;_,_,_,null-&gt;true;_,_,null,_-&gt;true;_,null,_,_-&gt;true;null,_,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.app.Velocity boolean mergeTemplate(java.lang.String, org.apache.velocity.context.Context, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;true;_,!null,_-&gt;true;_,_,!null-&gt;true;_,_,null-&gt;true;_,null,_-&gt;true;null,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.app.VelocityEngine boolean evaluate(org.apache.velocity.context.Context, java.io.Writer, java.lang.String, java.io.InputStream) 3'>
@@ -31,7 +31,7 @@
</item>
<item name='org.apache.velocity.app.VelocityEngine boolean mergeTemplate(java.lang.String, java.lang.String, org.apache.velocity.context.Context, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_-&gt;true;_,!null,_,_-&gt;true;_,_,!null,_-&gt;true;_,_,_,!null-&gt;true;_,_,_,null-&gt;true;_,_,null,_-&gt;true;_,null,_,_-&gt;true;null,_,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_,_-&gt;true&quot;"/>
</annotation>
</item>
</root>

View File

@@ -131,7 +131,7 @@
</item>
<item name='org.apache.velocity.app.event.implement.ReportInvalidReferences boolean invalidSetMethod(org.apache.velocity.context.Context, java.lang.String, java.lang.String, org.apache.velocity.util.introspection.Info)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_-&gt;false;_,!null,_,_-&gt;false;_,_,!null,_-&gt;false;_,_,_,!null-&gt;false;_,_,_,null-&gt;false;_,_,null,_-&gt;false;_,null,_,_-&gt;false;null,_,_,_-&gt;false&quot;"/>
<val val="&quot;_,_,_,_-&gt;false&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.app.event.implement.ReportInvalidReferences boolean invalidSetMethod(org.apache.velocity.context.Context, java.lang.String, java.lang.String, org.apache.velocity.util.introspection.Info) 0'>
@@ -145,7 +145,7 @@
</item>
<item name='org.apache.velocity.app.event.implement.ReportInvalidReferences java.lang.Object invalidGetMethod(org.apache.velocity.context.Context, java.lang.String, java.lang.Object, java.lang.String, org.apache.velocity.util.introspection.Info)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_,_-&gt;null;_,!null,_,_,_-&gt;null;_,_,!null,_,_-&gt;null;_,_,_,!null,_-&gt;null;_,_,_,_,!null-&gt;null;_,_,_,_,null-&gt;null;_,_,_,null,_-&gt;null;_,_,null,_,_-&gt;null;_,null,_,_,_-&gt;null;null,_,_,_,_-&gt;null&quot;"/>
<val val="&quot;_,_,_,_,_-&gt;null&quot;"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
@@ -163,7 +163,7 @@
</item>
<item name='org.apache.velocity.app.event.implement.ReportInvalidReferences java.lang.Object invalidMethod(org.apache.velocity.context.Context, java.lang.String, java.lang.Object, java.lang.String, org.apache.velocity.util.introspection.Info)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_,_-&gt;null;_,!null,_,_,_-&gt;null;_,_,!null,_,_-&gt;null;_,_,_,!null,_-&gt;null;_,_,_,_,!null-&gt;null;_,_,_,_,null-&gt;null;_,_,_,null,_-&gt;null;_,_,null,_,_-&gt;null;_,null,_,_,_-&gt;null;null,_,_,_,_-&gt;null&quot;"/>
<val val="&quot;_,_,_,_,_-&gt;null&quot;"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>

View File

@@ -70,6 +70,7 @@
</item>
<item name='org.apache.velocity.io.VelocityWriter void bufferOverflow()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -43,7 +43,7 @@
</item>
<item name='org.apache.velocity.runtime.RuntimeInstance boolean render(org.apache.velocity.context.Context, java.io.Writer, java.lang.String, org.apache.velocity.runtime.parser.node.SimpleNode)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_-&gt;true;_,!null,_,_-&gt;true;_,_,!null,_-&gt;true;_,_,_,!null-&gt;true;_,_,_,null-&gt;true;_,_,null,_-&gt;true;_,null,_,_-&gt;true;null,_,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.RuntimeInstance java.lang.Object getProperty(java.lang.String)'>
@@ -166,7 +166,7 @@
</item>
<item name='org.apache.velocity.runtime.VelocimacroManager boolean addVM(java.lang.String, org.apache.velocity.runtime.parser.node.Node, java.lang.String[], java.lang.String, boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_,_-&gt;true;_,!null,_,_,_-&gt;true;_,_,!null,_,_-&gt;true;_,_,_,!null,_-&gt;true;_,_,_,null,_-&gt;true;_,_,null,_,_-&gt;true;null,_,_,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.VelocimacroManager boolean addVM(java.lang.String, org.apache.velocity.runtime.parser.node.Node, java.lang.String[], java.lang.String, boolean) 1'>

View File

@@ -62,7 +62,7 @@
</item>
<item name='org.apache.velocity.runtime.directive.Break boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;false;_,!null,_-&gt;false;_,_,!null-&gt;false;_,null,_-&gt;false;null,_,_-&gt;false&quot;"/>
<val val="&quot;_,_,_-&gt;false&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.directive.Break boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node) 1'>
@@ -95,7 +95,7 @@
</item>
<item name='org.apache.velocity.runtime.directive.Define boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;true;_,!null,_-&gt;true;_,_,!null-&gt;true;_,_,null-&gt;true;_,null,_-&gt;true&quot;"/>
<val val="&quot;_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.directive.Define boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node) 0'>
@@ -288,7 +288,7 @@
</item>
<item name='org.apache.velocity.runtime.directive.Include boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;true;_,!null,_-&gt;true;_,_,!null-&gt;true;_,null,_-&gt;true;null,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.directive.Include boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node) 2'>
@@ -333,7 +333,7 @@
</item>
<item name='org.apache.velocity.runtime.directive.Literal boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_-&gt;true;_,!null,_-&gt;true;_,_,!null-&gt;true;_,_,null-&gt;true;null,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.directive.Literal boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node) 0'>
@@ -374,8 +374,7 @@
</item>
<item name='org.apache.velocity.runtime.directive.Macro boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value"
val="&quot;!null,_,_-&gt;true;_,!null,_-&gt;true;_,_,!null-&gt;true;_,_,null-&gt;true;_,null,_-&gt;true;null,_,_-&gt;true&quot;"/>
<val name="value" val="&quot;_,_,_-&gt;true&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -598,7 +597,7 @@
</item>
<item name='org.apache.velocity.runtime.directive.VelocimacroProxy boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node, org.apache.velocity.runtime.Renderable)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_,_,_-&gt;true;_,!null,_,_-&gt;true;_,_,!null,_-&gt;true;_,_,_,!null-&gt;true;_,_,_,null-&gt;true;_,null,_,_-&gt;true;null,_,_,_-&gt;true&quot;"/>
<val val="&quot;_,_,_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.directive.VelocimacroProxy boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer, org.apache.velocity.runtime.parser.node.Node, org.apache.velocity.runtime.Renderable) 2'>

View File

@@ -233,6 +233,7 @@
</item>
<item name='org.apache.velocity.runtime.log.RuntimeLoggerLog void setLogChute(org.apache.velocity.runtime.log.LogChute)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -241,6 +242,7 @@
</item>
<item name='org.apache.velocity.runtime.log.RuntimeLoggerLog void setShowStackTraces(boolean)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -65,7 +65,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTBlock boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;_,null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTBlock java.lang.Object jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) 0'>
@@ -83,7 +83,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTComment boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTComment boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) 0'>
@@ -115,7 +115,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTDirective boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;_,null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTDirective java.lang.Object init(org.apache.velocity.context.InternalContextAdapter, java.lang.Object)'>
@@ -185,7 +185,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTElseStatement boolean evaluate(org.apache.velocity.context.InternalContextAdapter)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val name="value" val="&quot;_-&gt;true&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -207,7 +207,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTEscape boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTEscape boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) 0'>
@@ -239,7 +239,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTEscapedDirective boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTEscapedDirective boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) 0'>
@@ -276,7 +276,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTFalse boolean evaluate(org.apache.velocity.context.InternalContextAdapter)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -382,7 +382,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTIfStatement boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;_,null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTIfStatement java.lang.Object jjtAccept(org.apache.velocity.runtime.parser.node.ParserVisitor, java.lang.Object) 0'>
@@ -531,8 +531,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTMathNode java.lang.Object handleSpecial(java.lang.Object, java.lang.Object, org.apache.velocity.context.InternalContextAdapter)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value"
val="&quot;!null,_,_-&gt;null;_,!null,_-&gt;null;_,_,!null-&gt;null;_,_,null-&gt;null;_,null,_-&gt;null;null,_,_-&gt;null&quot;"/>
<val name="value" val="&quot;_,_,_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -726,7 +725,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTReference boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;_,null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTReference java.lang.Object execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter)'>
@@ -855,7 +854,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTText boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTText boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) 0'>
@@ -887,7 +886,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTTextblock boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTTextblock boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer) 0'>
@@ -919,7 +918,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.ASTTrue boolean evaluate(org.apache.velocity.context.InternalContextAdapter)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val name="value" val="&quot;_-&gt;true&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1136,7 +1135,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.SimpleNode boolean evaluate(org.apache.velocity.context.InternalContextAdapter)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -1150,7 +1149,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.SimpleNode boolean render(org.apache.velocity.context.InternalContextAdapter, java.io.Writer)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val val="&quot;!null,_-&gt;true;_,!null-&gt;true;_,null-&gt;true;null,_-&gt;true&quot;"/>
<val val="&quot;_,_-&gt;true&quot;"/>
</annotation>
</item>
<item name='org.apache.velocity.runtime.parser.node.SimpleNode int getColumn()'>
@@ -1185,7 +1184,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.SimpleNode java.lang.Object execute(java.lang.Object, org.apache.velocity.context.InternalContextAdapter)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null,_-&gt;null;_,!null-&gt;null;_,null-&gt;null;null,_-&gt;null&quot;"/>
<val name="value" val="&quot;_,_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>
@@ -1206,7 +1205,7 @@
</item>
<item name='org.apache.velocity.runtime.parser.node.SimpleNode java.lang.Object value(org.apache.velocity.context.InternalContextAdapter)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;null;null-&gt;null&quot;"/>
<val name="value" val="&quot;_-&gt;null&quot;"/>
<val name="pure" val="true"/>
</annotation>
<annotation name='org.jetbrains.annotations.Nullable'/>

View File

@@ -6,7 +6,7 @@
</item>
<item name='org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader boolean isSourceModified(org.apache.velocity.runtime.resource.Resource)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;false;null-&gt;false&quot;"/>
<val name="value" val="&quot;_-&gt;false&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>
@@ -116,7 +116,7 @@
</item>
<item name='org.apache.velocity.runtime.resource.loader.JarResourceLoader boolean isSourceModified(org.apache.velocity.runtime.resource.Resource)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;!null-&gt;true;null-&gt;true&quot;"/>
<val name="value" val="&quot;_-&gt;true&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -1,6 +1,7 @@
<root>
<item name='org.apache.velocity.servlet.VelocityServlet org.apache.velocity.Template handleRequest(org.apache.velocity.context.Context)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="value" val="&quot;_-&gt;fail&quot;"/>
<val name="pure" val="true"/>
</annotation>
</item>

View File

@@ -123,13 +123,29 @@ public class Test01 {
return s::trim;
}
@ExpectContract(pure = true)
@ExpectContract(pure = true, value="null,_->fail")
public static void assertNotNull(@ExpectNotNull Object obj, String message) {
if(obj == null) {
throw new IllegalArgumentException(message);
}
}
@ExpectContract(pure = true, value="false,_,_->fail;true,_,_->true")
public static boolean assertTrue(boolean val, String message, int data) {
if(!val) {
throw new IllegalArgumentException(message+":"+data);
}
return val;
}
@ExpectContract(pure = true, value="true,_->fail;_,_->false")
public static boolean assertFalse(boolean val, String message) {
if(val) {
throw new IllegalArgumentException(message);
}
return false;
}
@ExpectNotNull
@ExpectContract(pure = true)
public static long[] copyOfRange(@ExpectNotNull long[] arr, int from, int to) {
@@ -154,4 +170,34 @@ public class Test01 {
System.arraycopy(arr, from, copy, 0, Math.min(arr.length - from, diff));
return copy;
}
@ExpectContract(value = "_->fail", pure = true)
public static void callAlwaysFail(int x) {
alwaysFail();
}
@ExpectContract(value = "_->fail", pure = true)
public static void callAlwaysFailRef(String x) {
callAlwaysFailTwoRefs(x, null);
}
@ExpectContract(value = "_,_->fail", pure = true)
public static void callAlwaysFailTwoRefs(String x, String y) {
alwaysFail();
}
@ExpectContract(value = "->fail", pure = true)
private static void alwaysFail() {
throw new UnsupportedOperationException();
}
@ExpectContract(value = "!null->null;null->!null", pure = true)
static String invert(String x) {
return x == null ? "empty" : null;
}
@ExpectContract(value = "false->true;true->false", pure = true)
static boolean invertBool(boolean x) {
return !x;
}
}