Custom relation-based contracts; hardcoded contracts for string methods (charAt, substring)

This commit is contained in:
Tagir Valeev
2017-04-11 10:46:44 +07:00
parent 3457fe2b55
commit 761a376ad2
34 changed files with 1033 additions and 656 deletions
@@ -18,11 +18,11 @@ package com.intellij.codeInsight;
import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiMethodImpl;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.containers.ContainerUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -162,8 +162,8 @@ public class InferredAnnotationsManagerImpl extends InferredAnnotationsManager {
}
@Nullable
private PsiAnnotation createContractAnnotation(List<MethodContract> contracts, boolean pure) {
return createContractAnnotation(myProject, pure, StringUtil.join(contracts, "; "));
private PsiAnnotation createContractAnnotation(List<? extends MethodContract> contracts, boolean pure) {
return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; "));
}
@Nullable
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
@@ -31,18 +32,18 @@ import java.util.Set;
*/
class ContractChecker extends DataFlowRunner {
private final PsiMethod myMethod;
private final MethodContract myContract;
private final StandardMethodContract myContract;
private final Set<PsiElement> myViolations = ContainerUtil.newHashSet();
private final Set<PsiElement> myNonViolations = ContainerUtil.newHashSet();
private final Set<PsiElement> myFailures = ContainerUtil.newHashSet();
private ContractChecker(PsiMethod method, MethodContract contract) {
private ContractChecker(PsiMethod method, StandardMethodContract contract) {
super(false, true);
myMethod = method;
myContract = contract;
}
static Map<PsiElement, String> checkContractClause(PsiMethod method, MethodContract contract, boolean ignoreAssertions) {
static Map<PsiElement, String> checkContractClause(PsiMethod method, StandardMethodContract contract, boolean ignoreAssertions) {
PsiCodeBlock body = method.getBody();
if (body == null) return Collections.emptyMap();
@@ -58,7 +59,7 @@ class ContractChecker extends DataFlowRunner {
if (comparisonValue != null) {
boolean negated = constraint.shouldUseNonEqComparison();
DfaVariableValue dfaParam = factory.getVarFactory().createVariableValue(parameters[i], false);
initialState.applyCondition(factory.getRelationFactory().createRelation(dfaParam, comparisonValue, JavaTokenType.EQEQ, negated));
initialState.applyCondition(factory.createCondition(dfaParam, RelationType.equivalence(!negated), comparisonValue));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* 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.
@@ -43,7 +43,7 @@ public class ContractInference {
public static final int MAX_CONTRACT_COUNT = 10;
@NotNull
public static List<MethodContract> inferContracts(@NotNull PsiMethodImpl method) {
public static List<StandardMethodContract> inferContracts(@NotNull PsiMethodImpl method) {
if (!InferenceFromSourceUtil.shouldInferFromSource(method)) {
return Collections.emptyList();
}
@@ -51,22 +51,22 @@ public class ContractInference {
return CachedValuesManager.getCachedValue(method, () -> {
MethodData data = ContractInferenceIndexKt.getIndexedData(method);
List<PreContract> preContracts = data == null ? Collections.emptyList() : data.getContracts();
List<MethodContract> result = RecursionManager.doPreventingRecursion(method, true, () -> postProcessContracts(method, data, preContracts));
List<StandardMethodContract> result = RecursionManager.doPreventingRecursion(method, true, () -> postProcessContracts(method, data, preContracts));
if (result == null) result = Collections.emptyList();
return CachedValueProvider.Result.create(result, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
});
}
@NotNull
private static List<MethodContract> postProcessContracts(@NotNull PsiMethodImpl method, MethodData data, List<PreContract> rawContracts) {
List<MethodContract> contracts = ContainerUtil.concat(rawContracts, c -> c.toContracts(method, data.methodBody(method)));
private static List<StandardMethodContract> postProcessContracts(@NotNull PsiMethodImpl method, MethodData data, List<PreContract> rawContracts) {
List<StandardMethodContract> contracts = ContainerUtil.concat(rawContracts, c -> c.toContracts(method, data.methodBody(method)));
if (contracts.isEmpty()) return Collections.emptyList();
final PsiType returnType = method.getReturnType();
if (returnType != null && !(returnType instanceof PsiPrimitiveType)) {
contracts = boxReturnValues(contracts);
}
List<MethodContract> compatible = ContainerUtil.filter(contracts, contract -> isContractCompatibleWithMethod(method, returnType, contract));
List<StandardMethodContract> compatible = ContainerUtil.filter(contracts, contract -> isContractCompatibleWithMethod(method, returnType, contract));
if (compatible.size() > MAX_CONTRACT_COUNT) {
LOG.debug("Too many contracts for " + PsiUtil.getMemberQualifiedName(method) + ", shrinking the list");
return compatible.subList(0, MAX_CONTRACT_COUNT);
@@ -74,14 +74,14 @@ public class ContractInference {
return compatible;
}
private static boolean isContractCompatibleWithMethod(@NotNull PsiMethod method, PsiType returnType, MethodContract contract) {
private static boolean isContractCompatibleWithMethod(@NotNull PsiMethod method, PsiType returnType, StandardMethodContract contract) {
if (hasContradictoryExplicitParameterNullity(method, contract)) return false;
if (isReturnNullitySpecifiedExplicitly(method, contract)) return false;
if (isContradictingExplicitNullableReturn(method, contract)) return false;
return InferenceFromSourceUtil.isReturnTypeCompatible(returnType, contract.returnValue);
}
private static boolean hasContradictoryExplicitParameterNullity(@NotNull PsiMethod method, MethodContract contract) {
private static boolean hasContradictoryExplicitParameterNullity(@NotNull PsiMethod method, StandardMethodContract contract) {
for (int i = 0; i < contract.arguments.length; i++) {
if (contract.arguments[i] == NULL_VALUE && NullableNotNullManager.isNotNull(method.getParameterList().getParameters()[i])) {
return true;
@@ -90,13 +90,13 @@ public class ContractInference {
return false;
}
private static boolean isContradictingExplicitNullableReturn(@NotNull PsiMethod method, MethodContract contract) {
private static boolean isContradictingExplicitNullableReturn(@NotNull PsiMethod method, StandardMethodContract contract) {
return contract.returnValue == NOT_NULL_VALUE &&
Arrays.stream(contract.arguments).allMatch(c -> c == ANY_VALUE) &&
NullableNotNullManager.getInstance(method.getProject()).isNullable(method, false);
}
private static boolean isReturnNullitySpecifiedExplicitly(@NotNull PsiMethod method, MethodContract contract) {
private static boolean isReturnNullitySpecifiedExplicitly(@NotNull PsiMethod method, StandardMethodContract contract) {
if (contract.returnValue != NOT_NULL_VALUE && contract.returnValue != NULL_VALUE) {
return false; // spare expensive nullity check
}
@@ -104,10 +104,10 @@ public class ContractInference {
}
@NotNull
private static List<MethodContract> boxReturnValues(List<MethodContract> contracts) {
private static List<StandardMethodContract> boxReturnValues(List<StandardMethodContract> contracts) {
return ContainerUtil.mapNotNull(contracts, contract -> {
if (contract.returnValue == FALSE_VALUE || contract.returnValue == TRUE_VALUE) {
return new MethodContract(contract.arguments, NOT_NULL_VALUE);
return new StandardMethodContract(contract.arguments, NOT_NULL_VALUE);
}
return contract;
});
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -65,7 +65,7 @@ class ContractInferenceInterpreter {
if (result != null) return result;
}
return visitStatements(singletonList(MethodContract.createConstraintArray(getParameters().size())), statements);
return visitStatements(singletonList(StandardMethodContract.createConstraintArray(getParameters().size())), statements);
}
@Nullable
@@ -168,11 +168,11 @@ class ContractInferenceInterpreter {
int paramIndex = resolveParameter(expr);
if (paramIndex >= 0) {
List<MethodContract> result = ContainerUtil.newArrayList();
List<StandardMethodContract> result = ContainerUtil.newArrayList();
for (ValueConstraint[] state : states) {
if (state[paramIndex] != ANY_VALUE) {
// the second 'o' reference in cases like: if (o != null) return o;
result.add(new MethodContract(state, state[paramIndex]));
result.add(new StandardMethodContract(state, state[paramIndex]));
} else if (JavaTokenType.BOOLEAN_KEYWORD == getPrimitiveParameterType(paramIndex)) {
// if (boolValue) ...
ContainerUtil.addIfNotNull(result, contractWithConstraint(state, paramIndex, TRUE_VALUE, TRUE_VALUE));
@@ -188,7 +188,7 @@ class ContractInferenceInterpreter {
@NotNull
private List<PreContract> visitPolyadic(List<ValueConstraint[]> states, @NotNull LighterASTNode expr) {
if (firstChildOfType(myTree, expr, JavaTokenType.PLUS) != null) {
return asPreContracts(ContainerUtil.map(states, s -> new MethodContract(s, NOT_NULL_VALUE)));
return asPreContracts(ContainerUtil.map(states, s -> new StandardMethodContract(s, NOT_NULL_VALUE)));
}
List<LighterASTNode> operands = getExpressionChildren(myTree, expr);
@@ -206,22 +206,22 @@ class ContractInferenceInterpreter {
}
@NotNull
private static List<PreContract> asPreContracts(List<MethodContract> contracts) {
private static List<PreContract> asPreContracts(List<StandardMethodContract> contracts) {
return ContainerUtil.map(contracts, KnownContract::new);
}
@Nullable
private static MethodContract contractWithConstraint(ValueConstraint[] state,
int parameter, ValueConstraint paramConstraint,
ValueConstraint returnValue) {
private static StandardMethodContract contractWithConstraint(ValueConstraint[] state,
int parameter, ValueConstraint paramConstraint,
ValueConstraint returnValue) {
ValueConstraint[] newState = withConstraint(state, parameter, paramConstraint);
return newState == null ? null : new MethodContract(newState, returnValue);
return newState == null ? null : new StandardMethodContract(newState, returnValue);
}
private List<MethodContract> visitEqualityComparison(List<ValueConstraint[]> states,
LighterASTNode op1,
LighterASTNode op2,
boolean equality) {
private List<StandardMethodContract> visitEqualityComparison(List<ValueConstraint[]> states,
LighterASTNode op1,
LighterASTNode op2,
boolean equality) {
int parameter = resolveParameter(op1);
ValueConstraint constraint = getLiteralConstraint(op2);
if (parameter < 0 || constraint == null) {
@@ -229,7 +229,7 @@ class ContractInferenceInterpreter {
constraint = getLiteralConstraint(op1);
}
if (parameter >= 0 && constraint != null) {
List<MethodContract> result = ContainerUtil.newArrayList();
List<StandardMethodContract> result = ContainerUtil.newArrayList();
for (ValueConstraint[] state : states) {
if (constraint == NOT_NULL_VALUE) {
if (getPrimitiveParameterType(parameter) == null) {
@@ -253,13 +253,13 @@ class ContractInferenceInterpreter {
return primitive == null ? null : primitive.getTokenType();
}
static List<MethodContract> toContracts(List<ValueConstraint[]> states, ValueConstraint constraint) {
return ContainerUtil.map(states, state -> new MethodContract(state, constraint));
static List<StandardMethodContract> toContracts(List<ValueConstraint[]> states, ValueConstraint constraint) {
return ContainerUtil.map(states, state -> new StandardMethodContract(state, constraint));
}
private List<MethodContract> visitLogicalOperation(List<LighterASTNode> operands, boolean conjunction, List<ValueConstraint[]> states) {
private List<StandardMethodContract> visitLogicalOperation(List<LighterASTNode> operands, boolean conjunction, List<ValueConstraint[]> states) {
ValueConstraint breakValue = conjunction ? FALSE_VALUE : TRUE_VALUE;
List<MethodContract> finalStates = ContainerUtil.newArrayList();
List<StandardMethodContract> finalStates = ContainerUtil.newArrayList();
for (LighterASTNode operand : operands) {
List<PreContract> opResults = visitExpression(states, operand);
finalStates.addAll(ContainerUtil.filter(knownContracts(opResults), contract -> contract.returnValue == breakValue));
@@ -269,7 +269,7 @@ class ContractInferenceInterpreter {
return finalStates;
}
private static List<MethodContract> knownContracts(List<PreContract> values) {
private static List<StandardMethodContract> knownContracts(List<PreContract> values) {
return ContainerUtil.mapNotNull(values, pc -> pc instanceof KnownContract ? ((KnownContract)pc).getContract() : null);
}
@@ -39,7 +39,7 @@ public class ContractInspection extends BaseJavaBatchLocalInspectionTool {
@Override
public void visitMethod(PsiMethod method) {
for (MethodContract contract : ControlFlowAnalyzer.getMethodContracts(method)) {
for (StandardMethodContract contract : ControlFlowAnalyzer.getMethodContracts(method)) {
Map<PsiElement, String> errors = ContractChecker.checkContractClause(method, contract, false);
for (Map.Entry<PsiElement, String> entry : errors.entrySet()) {
PsiElement element = entry.getKey();
@@ -78,16 +78,16 @@ public class ContractInspection extends BaseJavaBatchLocalInspectionTool {
@Nullable
public static String checkContract(PsiMethod method, String text) {
List<MethodContract> contracts;
List<StandardMethodContract> contracts;
try {
contracts = MethodContract.parseContract(text);
contracts = StandardMethodContract.parseContract(text);
}
catch (MethodContract.ParseException e) {
catch (StandardMethodContract.ParseException e) {
return e.getMessage();
}
int paramCount = method.getParameterList().getParametersCount();
for (int i = 0; i < contracts.size(); i++) {
MethodContract contract = contracts.get(i);
StandardMethodContract contract = contracts.get(i);
if (contract.arguments.length != paramCount) {
return "Method takes " + paramCount + " parameters, while contract clause number " + (i + 1) + " expects " + contract.arguments.length;
}
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.ExceptionUtil;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
@@ -787,8 +788,7 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
public DfaInstructionState[] accept(DataFlowRunner runner, DfaMemoryState state, InstructionVisitor visitor) {
DfaValue value = state.pop();
DfaValueFactory factory = runner.getFactory();
if (state.applyCondition(
factory.getRelationFactory().createRelation(value, factory.getConstFactory().getNull(), JavaTokenType.EQEQ, true))) {
if (state.applyCondition(factory.createCondition(value, RelationType.NE, factory.getConstFactory().getNull()))) {
return nextInstruction(runner, state);
}
if (visitor instanceof StandardInstructionVisitor) {
@@ -1338,9 +1338,10 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
}
addConditionalRuntimeThrow();
List<MethodContract> contracts = method instanceof PsiMethod ? getMethodCallContracts((PsiMethod)method, expression) : Collections.emptyList();
List<? extends MethodContract> contracts =
method instanceof PsiMethod ? getMethodCallContracts((PsiMethod)method, expression) : Collections.emptyList();
addInstruction(new MethodCallInstruction(expression, myFactory.createValue(expression), contracts));
if (contracts.stream().anyMatch(c -> c.returnValue == MethodContract.ValueConstraint.THROW_EXCEPTION)) {
if (contracts.stream().anyMatch(c -> c.getReturnValue() == MethodContract.ValueConstraint.THROW_EXCEPTION)) {
// if a contract resulted in 'fail', handle it
addInstruction(new DupInstruction());
addInstruction(new PushInstruction(myFactory.getConstFactory().getContractFail(), null));
@@ -1375,12 +1376,13 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
finishElement(expression);
}
private static List<MethodContract> getMethodCallContracts(@NotNull final PsiMethod method, @NotNull PsiMethodCallExpression call) {
private static List<? extends MethodContract> getMethodCallContracts(@NotNull final PsiMethod method,
@NotNull PsiMethodCallExpression call) {
List<MethodContract> contracts = HardcodedContracts.getHardcodedContracts(method, call);
return !contracts.isEmpty() ? contracts : getMethodContracts(method);
}
public static List<MethodContract> getMethodContracts(@NotNull final PsiMethod method) {
public static List<StandardMethodContract> getMethodContracts(@NotNull final PsiMethod method) {
return CachedValuesManager.getCachedValue(method, () -> {
final PsiAnnotation contractAnno = findContractAnnotation(method);
if (contractAnno != null) {
@@ -1388,15 +1390,16 @@ public class ControlFlowAnalyzer extends JavaElementVisitor {
if (text != null) {
try {
final int paramCount = method.getParameterList().getParametersCount();
List<MethodContract> applicable = ContainerUtil.filter(MethodContract.parseContract(text),
contract -> contract.arguments.length == paramCount);
List<StandardMethodContract> applicable = ContainerUtil.filter(StandardMethodContract.parseContract(text),
contract -> contract.arguments.length == paramCount);
return CachedValueProvider.Result.create(applicable, contractAnno, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
}
catch (Exception ignored) {
}
}
}
return CachedValueProvider.Result.create(Collections.<MethodContract>emptyList(), method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
return CachedValueProvider.Result
.create(Collections.<StandardMethodContract>emptyList(), method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
});
}
@@ -17,6 +17,7 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiType;
import com.intellij.util.ArrayUtil;
@@ -29,7 +30,6 @@ import java.util.Collections;
import java.util.List;
import static com.intellij.psi.CommonClassNames.*;
import static com.intellij.psi.JavaTokenType.*;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
/**
@@ -84,10 +84,10 @@ public class CustomMethodHandlers {
if (leftConst != null && rightConst != null) {
return singleResult(memState, factory.getBoolean(ends ? leftConst.endsWith(rightConst) : leftConst.startsWith(rightConst)));
}
DfaValue leftLength = memState.getStringLength(qualifier);
DfaValue rightLength = memState.getStringLength(arg);
DfaRelationValue trueRelation = factory.getRelationFactory().createRelation(leftLength, rightLength, GE, false);
DfaRelationValue falseRelation = factory.getRelationFactory().createRelation(leftLength, rightLength, LT, false);
DfaValue leftLength = factory.createStringLength(qualifier);
DfaValue rightLength = factory.createStringLength(arg);
DfaValue trueRelation = factory.createCondition(leftLength, RelationType.GE, rightLength);
DfaValue falseRelation = factory.createCondition(leftLength, RelationType.LT, rightLength);
return applyCondition(memState, trueRelation, DfaUnknownValue.getInstance(), falseRelation, factory.getBoolean(false));
}
@@ -103,30 +103,30 @@ public class CustomMethodHandlers {
if (leftConst != null && rightConst != null) {
return singleResult(memState, factory.getBoolean(ignoreCase ? leftConst.equalsIgnoreCase(rightConst) : leftConst.equals(rightConst)));
}
DfaValue leftLength = memState.getStringLength(qualifier);
DfaValue rightLength = memState.getStringLength(arg);
DfaRelationValue trueRelation = factory.getRelationFactory().createRelation(leftLength, rightLength, EQ, false);
DfaRelationValue falseRelation = factory.getRelationFactory().createRelation(leftLength, rightLength, NE, false);
DfaValue leftLength = factory.createStringLength(qualifier);
DfaValue rightLength = factory.createStringLength(arg);
DfaValue trueRelation = factory.createCondition(leftLength, RelationType.EQ, rightLength);
DfaValue falseRelation = factory.createCondition(leftLength, RelationType.NE, rightLength);
return applyCondition(memState, trueRelation, DfaUnknownValue.getInstance(), falseRelation, factory.getBoolean(false));
}
private static List<DfaMemoryState> stringIndexOf(DfaValue qualifier,
DfaMemoryState memState,
DfaValueFactory factory) {
DfaValue length = memState.getStringLength(qualifier);
DfaValue length = factory.createStringLength(qualifier);
LongRangeSet range = memState.getRange(length);
long maxLen = range == null || range.isEmpty() ? Integer.MAX_VALUE : range.max();
return singleResult(memState, factory.getRangeFactory().create(LongRangeSet.range(-1, maxLen - 1)));
}
private static List<DfaMemoryState> stringIsEmpty(DfaValue qualifier, DfaMemoryState memState, DfaValueFactory factory) {
DfaValue length = memState.getStringLength(qualifier);
DfaValue length = factory.createStringLength(qualifier);
if (length == DfaUnknownValue.getInstance()) {
return singleResult(memState, DfaUnknownValue.getInstance());
}
DfaConstValue zero = factory.getConstFactory().createFromValue(0, PsiType.INT, null);
DfaRelationValue trueRelation = factory.getRelationFactory().createRelation(length, zero, EQEQ, false);
DfaRelationValue falseRelation = factory.getRelationFactory().createRelation(length, zero, NE, false);
DfaValue trueRelation = factory.createCondition(length, RelationType.EQ, zero);
DfaValue falseRelation = factory.createCondition(length, RelationType.NE, zero);
return applyCondition(memState, trueRelation, factory.getBoolean(true), falseRelation, factory.getBoolean(false));
}
@@ -156,17 +156,17 @@ public class CustomMethodHandlers {
@NotNull
private static List<DfaMemoryState> applyCondition(DfaMemoryState memState,
DfaRelationValue trueRelation,
DfaValue trueCondition,
DfaValue trueResult,
DfaRelationValue falseRelation,
DfaValue falseCondition,
DfaValue falseResult) {
DfaMemoryState falseState = memState.createCopy();
List<DfaMemoryState> result = new ArrayList<>(2);
if (memState.applyCondition(trueRelation)) {
if (memState.applyCondition(trueCondition)) {
memState.push(trueResult);
result.add(memState);
}
if (falseState.applyCondition(falseRelation)) {
if (falseState.applyCondition(falseCondition)) {
falseState.push(falseResult);
result.add(falseState);
}
@@ -405,7 +405,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
for (PsiCall call : visitor.getAlwaysFailingCalls()) {
PsiMethod method = call.resolveMethod();
if (method != null && reportedAnchors.add(call)) {
holder.registerProblem(getElementToHighlight(call), "The call to #ref always fails, according to its method contracts");
holder.registerProblem(getElementToHighlight(call), "The call to '#ref' always fails, according to its method contracts");
}
}
}
@@ -1028,7 +1028,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
private static boolean isNonTrivialFailingContract(MethodContract contract) {
return contract.returnValue == MethodContract.ValueConstraint.THROW_EXCEPTION && !contract.isTrivial();
return contract.getReturnValue() == MethodContract.ValueConstraint.THROW_EXCEPTION && !contract.isTrivial();
}
@Override
@@ -48,17 +48,15 @@ public interface DfaMemoryState {
boolean applyInstanceofOrNull(@NotNull DfaRelationValue dfaCond);
void applyIsPresentCheck(boolean present, DfaValue qualifier);
boolean applyCondition(DfaValue dfaCond);
boolean applyContractCondition(DfaValue dfaCond);
ThreeState checkOptional(DfaValue value);
@Nullable
LongRangeSet getRange(DfaValue value);
DfaValue getStringLength(DfaValue value);
void flushFields();
void flushVariable(DfaVariableValue variable);
@@ -26,18 +26,22 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.UnorderedPair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.PsiEnumConstant;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import com.siyeh.ig.psiutils.MethodUtils;
import gnu.trove.*;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
@@ -265,7 +269,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
setVariableState(var, withValueNullability(value, getVariableState(var).withValue(value)));
if (value instanceof DfaTypeValue) {
DfaRelationValue dfaInstanceof = myFactory.getRelationFactory().createRelation(var, value, JavaTokenType.INSTANCEOF_KEYWORD, false);
DfaRelationValue dfaInstanceof = myFactory.getRelationFactory().createRelation(var, RelationType.IS, value);
if (((DfaTypeValue)value).isNotNull()) {
applyCondition(dfaInstanceof);
} else {
@@ -273,7 +277,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
}
else {
DfaRelationValue dfaEqual = myFactory.getRelationFactory().createRelation(var, value, JavaTokenType.EQEQ, false);
DfaRelationValue dfaEqual = myFactory.getRelationFactory().createRelation(var, RelationType.EQ, value);
if (dfaEqual == null) return;
applyCondition(dfaEqual);
@@ -283,7 +287,8 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
if (getVariableState(var).isNotNull()) {
applyCondition(compareToNull(var, true));
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
applyCondition(myFactory.getRelationFactory().createRelation(var, RelationType.NE, dfaNull));
}
}
@@ -650,8 +655,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return false;
}
@Override
public void applyIsPresentCheck(boolean present, DfaValue qualifier) {
private void applyIsPresentCheck(boolean present, DfaValue qualifier) {
if (qualifier instanceof DfaVariableValue && !isUnknownState(qualifier)) {
setVariableState((DfaVariableValue)qualifier, getVariableState((DfaVariableValue)qualifier).withOptionalPresense(present));
}
@@ -663,7 +667,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
}
public boolean applyRange(LongRangeSet range, DfaVariableValue target) {
boolean applyRange(LongRangeSet range, DfaVariableValue target) {
if (!isUnknownState(target) && range != null) {
DfaVariableState state = getVariableState(target);
LongRangeSet oldRange = state.getRange();
@@ -688,6 +692,21 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
return value;
}
@Override
public boolean applyContractCondition(DfaValue condition) {
if (condition instanceof DfaRelationValue) {
DfaRelationValue relation = (DfaRelationValue)condition;
if (relation.isEquality() &&
relation.getRightOperand() == myFactory.getConstFactory().getNull() &&
(relation.getLeftOperand() instanceof DfaUnknownValue ||
(relation.getLeftOperand() instanceof DfaVariableValue &&
getVariableState((DfaVariableValue)relation.getLeftOperand()).getNullability() == Nullness.UNKNOWN))) {
markEphemeral();
}
}
return applyCondition(condition);
}
@Override
public boolean applyCondition(DfaValue dfaCond) {
if (dfaCond instanceof DfaUnknownValue) return true;
@@ -696,14 +715,16 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
boolean isNegated = dfaVar.isNegated();
DfaVariableValue dfaNormalVar = isNegated ? dfaVar.createNegated() : dfaVar;
final DfaValue boxedTrue = myFactory.getBoxedFactory().createBoxed(myFactory.getConstFactory().getTrue());
return applyRelationCondition(myFactory.getRelationFactory().createRelation(dfaNormalVar, boxedTrue, JavaTokenType.EQEQ, isNegated));
return applyRelationCondition(
myFactory.getRelationFactory().createRelation(dfaNormalVar, RelationType.equivalence(!isNegated), boxedTrue));
}
if (dfaCond instanceof DfaVariableValue) {
DfaVariableValue dfaVar = (DfaVariableValue)dfaCond;
boolean isNegated = dfaVar.isNegated();
DfaVariableValue dfaNormalVar = isNegated ? dfaVar.createNegated() : dfaVar;
DfaConstValue dfaTrue = myFactory.getConstFactory().getTrue();
return applyRelationCondition(myFactory.getRelationFactory().createRelation(dfaNormalVar, dfaTrue, JavaTokenType.EQEQ, isNegated));
return applyRelationCondition(
myFactory.getRelationFactory().createRelation(dfaNormalVar, RelationType.equivalence(!isNegated), dfaTrue));
}
if (dfaCond instanceof DfaConstValue) {
@@ -719,13 +740,12 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
DfaValue dfaLeft = dfaRelation.getLeftOperand();
DfaValue dfaRight = dfaRelation.getRightOperand();
if (dfaLeft instanceof DfaUnknownValue || dfaRight instanceof DfaUnknownValue) return true;
boolean isNegated = dfaRelation.isNegated();
RelationType relationType = dfaRelation.getRelation();
if (dfaLeft instanceof DfaVariableValue) {
LongRangeSet right = getRange(dfaRight);
if (right != null) {
if (!applyRange(right.fromRelation(dfaRelation.getComparisonOperation()), (DfaVariableValue)dfaLeft)) {
if (!applyRange(right.fromRelation(relationType), (DfaVariableValue)dfaLeft)) {
return false;
}
}
@@ -733,15 +753,23 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
if (dfaRight instanceof DfaVariableValue) {
LongRangeSet left = getRange(dfaLeft);
if (left != null) {
if (!applyRange(left.fromRelation(DfaRelationValue.getSymmetricOperation(dfaRelation.getComparisonOperation())),
(DfaVariableValue)dfaRight)) {
if (!applyRange(left.fromRelation(relationType.getFlipped()), (DfaVariableValue)dfaRight)) {
return false;
}
}
}
if (dfaLeft instanceof DfaTypeValue && ((DfaTypeValue)dfaLeft).isNotNull() && dfaRight == myFactory.getConstFactory().getNull()) {
return isNegated;
if (dfaRight instanceof DfaOptionalValue && (relationType == RelationType.IS || relationType == RelationType.IS_NOT)) {
ThreeState state = checkOptional(dfaLeft);
boolean present = ((DfaOptionalValue)dfaRight).isPresent();
if (relationType == RelationType.IS_NOT) {
present = !present;
}
if (state == ThreeState.UNSURE) {
applyIsPresentCheck(present, dfaLeft);
return true;
}
return state == ThreeState.fromBoolean(present);
}
if (dfaRight instanceof DfaTypeValue) {
@@ -749,42 +777,43 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
DfaVariableValue dfaVar = (DfaVariableValue)dfaLeft;
if (isUnknownState(dfaVar)) return true;
if (!dfaRelation.isInstanceOf()) {
if (((DfaTypeValue)dfaRight).isNotNull() && isNull(dfaVar)) {
return isNegated;
DfaTypeValue typeValue = (DfaTypeValue)dfaRight;
switch (relationType) {
case EQ:
case NE:
return !(dfaRelation.isEquality() && typeValue.isNotNull() && isNull(dfaVar));
case IS_NOT: {
DfaVariableState newState = getVariableState(dfaVar).withNotInstanceofValue(typeValue);
if (newState != null) {
setVariableState(dfaVar, newState);
return true;
}
return !getVariableState(dfaVar).isNotNull() && applyRelation(dfaVar, myFactory.getConstFactory().getNull(), false);
}
return true;
case IS:
if (applyRelation(dfaVar, myFactory.getConstFactory().getNull(), true)) {
DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue(typeValue);
if (newState != null) {
setVariableState(dfaVar, newState);
return true;
}
}
return false;
default:
}
if (isNegated) {
DfaVariableState newState = getVariableState(dfaVar).withNotInstanceofValue((DfaTypeValue)dfaRight);
if (newState != null) {
setVariableState(dfaVar, newState);
return true;
}
return !getVariableState(dfaVar).isNotNull() && applyRelation(dfaVar, myFactory.getConstFactory().getNull(), false);
}
if (applyRelation(dfaVar, myFactory.getConstFactory().getNull(), true)) {
DfaVariableState newState = getVariableState(dfaVar).withInstanceofValue((DfaTypeValue)dfaRight);
if (newState != null) {
setVariableState(dfaVar, newState);
return true;
}
}
return false;
}
return true;
}
if (isEffectivelyNaN(dfaLeft) || isEffectivelyNaN(dfaRight)) {
applyEquivalenceRelation(dfaRelation, dfaLeft, dfaRight);
return isNegated;
return relationType == RelationType.NE;
}
if (canBeNaN(dfaLeft) && canBeNaN(dfaRight)) {
if (dfaLeft == dfaRight &&
dfaLeft instanceof DfaVariableValue &&
!(((DfaVariableValue)dfaLeft).getVariableType() instanceof PsiPrimitiveType)) {
return !isNegated;
return !dfaRelation.isNonEquality();
}
applyEquivalenceRelation(dfaRelation, dfaLeft, dfaRight);
@@ -899,12 +928,7 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
static boolean isNaN(final DfaValue dfa) {
if (dfa instanceof DfaConstValue) {
Object value = ((DfaConstValue)dfa).getValue();
if (value instanceof Double && ((Double)value).isNaN()) return true;
if (value instanceof Float && ((Float)value).isNaN()) return true;
}
return false;
return dfa instanceof DfaConstValue && DfaUtil.isNaN(((DfaConstValue)dfa).getValue());
}
private boolean applyRelation(@NotNull final DfaValue dfaLeft, @NotNull final DfaValue dfaRight, boolean isNegated) {
@@ -1027,50 +1051,18 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
}
return LongRangeSet.fromType(var.getVariableType());
}
return range;
}
if (value instanceof DfaRangeValue) {
return ((DfaRangeValue)value).getValue();
}
if (value instanceof DfaConstValue) {
return LongRangeSet.fromConstant(((DfaConstValue)value).getValue());
}
return null;
}
@Override
public DfaValue getStringLength(DfaValue value) {
if (value instanceof DfaVariableValue) {
DfaVariableValue variableValue = (DfaVariableValue)value;
DfaConstValue constValue = getConstantValue(variableValue);
if(constValue != null) {
value = constValue;
} else {
PsiType type = variableValue.getVariableType();
if (type != null && type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (psiClass != null) {
PsiMethod[] lengthMethods = psiClass.findMethodsByName("length", false);
if (lengthMethods.length == 1) {
return getFactory().getVarFactory().createVariableValue(lengthMethods[0], PsiType.INT, false, variableValue);
}
if (var.getPsiVariable() instanceof PsiMethod && MethodUtils.isStringLength((PsiMethod)var.getPsiVariable())) {
DfaVariableValue qualifier = var.getQualifier();
if(qualifier != null) {
DfaConstValue constValue = getConstantValue(qualifier);
if (constValue != null && constValue.getValue() instanceof String) {
return LongRangeSet.point(((String)constValue.getValue()).length());
}
}
}
return range;
}
if(value instanceof DfaConstValue) {
Object str = ((DfaConstValue)value).getValue();
if(str instanceof String) {
return getFactory().getRangeFactory().create(LongRangeSet.point(((String)str).length()));
}
}
return DfaUnknownValue.getInstance();
}
@Nullable
private DfaRelationValue compareToNull(DfaValue dfaVar, boolean negated) {
DfaConstValue dfaNull = myFactory.getConstFactory().getNull();
return myFactory.getRelationFactory().createRelation(dfaVar, dfaNull, JavaTokenType.EQEQ, negated);
return LongRangeSet.fromDfaValue(value);
}
void setVariableState(DfaVariableValue dfaVar, DfaVariableState state) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* 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.
@@ -255,4 +255,10 @@ public class DfaUtil {
return concatenation.getHead();
}
}
public static boolean isNaN(Object value) {
if (value instanceof Double && ((Double)value).isNaN()) return true;
if (value instanceof Float && ((Float)value).isNaN()) return true;
return false;
}
}
@@ -15,65 +15,32 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.value.DfaOptionalValue;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import one.util.streamex.IntStreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint.*;
import static com.intellij.codeInspection.dataFlow.MethodContract.createConstraintArray;
import static com.intellij.codeInspection.dataFlow.StandardMethodContract.createConstraintArray;
/**
* @author peter
*/
public class HardcodedContracts {
static class OptionalPresenceContract extends MethodContract.QualifierBasedContract {
private final boolean myPresent;
public OptionalPresenceContract(boolean mustPresent, ValueConstraint[] valueConstraints, ValueConstraint returnValue) {
super(valueConstraints, returnValue);
myPresent = mustPresent;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass() || !super.equals(o)) return false;
return myPresent == ((OptionalPresenceContract)o).myPresent;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + (myPresent ? 1 : 0);
}
@Override
boolean applyContract(boolean matches, DfaValue qualifier, DfaMemoryState memoryState) {
boolean present = !matches ^ myPresent;
ThreeState state = memoryState.checkOptional(qualifier);
if(state == ThreeState.fromBoolean(!present)) return false;
if(state == ThreeState.UNSURE) {
memoryState.applyIsPresentCheck(present, qualifier);
}
return true;
}
@Override
public String toString() {
return "[" + (myPresent ? "present" : "absent") + "] " + super.toString();
}
}
public static List<MethodContract> getHardcodedContracts(@NotNull PsiMethod method, @Nullable PsiMethodCallExpression call) {
PsiClass owner = method.getContainingClass();
if (owner == null ||
@@ -89,7 +56,7 @@ public class HardcodedContracts {
if ("java.lang.System".equals(className)) {
if ("exit".equals(methodName)) {
return Collections.singletonList(new MethodContract(createConstraintArray(paramCount), THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(createConstraintArray(paramCount), THROW_EXCEPTION));
}
}
else if ("com.google.common.base.Preconditions".equals(className)) {
@@ -99,7 +66,7 @@ public class HardcodedContracts {
if (("checkArgument".equals(methodName) || "checkState".equals(methodName)) && paramCount > 0) {
MethodContract.ValueConstraint[] constraints = createConstraintArray(paramCount);
constraints[0] = FALSE_VALUE;
return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(constraints, THROW_EXCEPTION));
}
}
else if ("java.util.Objects".equals(className)) {
@@ -107,18 +74,35 @@ public class HardcodedContracts {
return failIfNull(0, paramCount);
}
}
else if ("java.lang.String".equals(className)) {
if (("charAt".equals(methodName) || "codePointAt".equals(methodName)) && paramCount == 1) {
return Arrays.asList(new NonnegativeArgumentContract(1, 0),
new StringLengthContract(1, 0, RelationType.LT));
}
else if (("substring".equals(methodName) || "subSequence".equals(methodName)) && paramCount <= 2) {
List<MethodContract> contracts = new ArrayList<>(5);
contracts.add(new NonnegativeArgumentContract(paramCount, 0));
contracts.add(new StringLengthContract(paramCount, 0, RelationType.LE));
if (paramCount == 2) {
contracts.add(new NonnegativeArgumentContract(paramCount, 1));
contracts.add(new StringLengthContract(paramCount, 1, RelationType.LE));
contracts.add(new ArgumentRelationContract(paramCount, 0, RelationType.LE, 1));
}
return contracts;
}
}
else if ("org.apache.commons.lang.Validate".equals(className) ||
"org.apache.commons.lang3.Validate".equals(className) ||
"org.springframework.util.Assert".equals(className)) {
if (("isTrue".equals(methodName) || "state".equals(methodName)) && paramCount > 0) {
MethodContract.ValueConstraint[] constraints = createConstraintArray(paramCount);
constraints[0] = FALSE_VALUE;
return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(constraints, THROW_EXCEPTION));
}
if ("notNull".equals(methodName) && paramCount > 0) {
MethodContract.ValueConstraint[] constraints = createConstraintArray(paramCount);
constraints[0] = NULL_VALUE;
return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(constraints, THROW_EXCEPTION));
}
}
else if (isJunit(className) || isTestng(className) ||
@@ -127,20 +111,135 @@ public class HardcodedContracts {
return handleTestFrameworks(paramCount, className, methodName, call);
}
else if (TypeUtils.isOptional(owner)) {
MethodContract.ValueConstraint[] constraints = createConstraintArray(paramCount);
if (DfaOptionalSupport.isOptionalGetMethodName(methodName) || "orElseThrow".equals(methodName)) {
return Arrays.asList(new OptionalPresenceContract(false, constraints, THROW_EXCEPTION),
new OptionalPresenceContract(true, constraints, NOT_NULL_VALUE));
return Arrays.asList(new OptionalPresenceContract(false, THROW_EXCEPTION),
new OptionalPresenceContract(true, NOT_NULL_VALUE));
}
else if ("isPresent".equals(methodName)) {
return Arrays.asList(new OptionalPresenceContract(false, constraints, FALSE_VALUE),
new OptionalPresenceContract(true, constraints, TRUE_VALUE));
return Arrays.asList(new OptionalPresenceContract(false, FALSE_VALUE),
new OptionalPresenceContract(true, TRUE_VALUE));
}
}
return Collections.emptyList();
}
static class OptionalPresenceContract extends MethodContract {
private final boolean myPresent;
private final ValueConstraint myReturnValue;
public OptionalPresenceContract(boolean mustPresent, ValueConstraint returnValue) {
myReturnValue = returnValue;
myPresent = mustPresent;
}
@Override
List<DfaValue> getConditions(DfaValueFactory factory, DfaValue qualifier, DfaValue[] arguments) {
DfaOptionalValue optional = factory.getOptionalFactory().getOptional(myPresent);
return Collections.singletonList(factory.createCondition(qualifier, RelationType.IS, optional));
}
@Override
String getArgumentsPresentation() {
return "[" + (myPresent ? "present" : "absent") + "]";
}
@Override
public ValueConstraint getReturnValue() {
return myReturnValue;
}
}
static abstract class ArgumentRangeContract extends MethodContract {
final int myParamCount;
final int myIndex;
final RelationType myRelationType;
ArgumentRangeContract(int paramCount, int index, RelationType type) {
myParamCount = paramCount;
myIndex = index;
myRelationType = type;
}
@Override
List<DfaValue> getConditions(DfaValueFactory factory, DfaValue qualifier, DfaValue[] arguments) {
DfaValue left = arguments[myIndex];
DfaValue right = getBound(factory, qualifier, arguments);
return Collections.singletonList(factory.createCondition(left, myRelationType.getNegated(), right));
}
@NotNull
abstract DfaValue getBound(DfaValueFactory factory, DfaValue qualifier, DfaValue[] arguments);
abstract String getBoundRepresentation();
@Override
String getArgumentsPresentation() {
return IntStreamEx.range(myParamCount)
.mapToObj(idx -> idx == myIndex ? myRelationType.getNegated() + getBoundRepresentation() : "_")
.joining(", ");
}
@Override
public ValueConstraint getReturnValue() {
return THROW_EXCEPTION;
}
}
static class NonnegativeArgumentContract extends ArgumentRangeContract {
public NonnegativeArgumentContract(int paramCount, int nonNegativeArgumentIndex) {
super(paramCount, nonNegativeArgumentIndex, RelationType.GE);
}
@NotNull
@Override
DfaValue getBound(DfaValueFactory factory, DfaValue qualifier, DfaValue[] arguments) {
return factory.getConstFactory().createFromValue(0, PsiType.INT, null);
}
@Override
String getBoundRepresentation() {
return "0";
}
}
static class ArgumentRelationContract extends ArgumentRangeContract {
private final int myIndex;
public ArgumentRelationContract(int paramCount, int leftIndex, RelationType relationType, int rightIndex) {
super(paramCount, leftIndex, relationType);
myIndex = rightIndex;
}
@NotNull
@Override
DfaValue getBound(DfaValueFactory factory, DfaValue qualifier, DfaValue[] arguments) {
return arguments[myIndex];
}
@Override
String getBoundRepresentation() {
return "arg#" + myIndex;
}
}
static class StringLengthContract extends ArgumentRangeContract {
StringLengthContract(int paramCount, int index, RelationType type) {
super(paramCount, index, type);
}
@NotNull
@Override
DfaValue getBound(DfaValueFactory factory, DfaValue qualifier, DfaValue[] arguments) {
return factory.createStringLength(qualifier);
}
@Override
String getBoundRepresentation() {
return "this.length()";
}
}
private static boolean isJunit(String className) {
return className.startsWith("junit.framework.") || className.startsWith("org.junit.");
}
@@ -182,7 +281,7 @@ public class HardcodedContracts {
boolean testng = isTestng(className);
if ("fail".equals(methodName)) {
return Collections.singletonList(new MethodContract(createConstraintArray(paramCount), THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(createConstraintArray(paramCount), THROW_EXCEPTION));
}
if (paramCount == 0) return Collections.emptyList();
@@ -191,15 +290,15 @@ public class HardcodedContracts {
MethodContract.ValueConstraint[] constraints = createConstraintArray(paramCount);
if ("assertTrue".equals(methodName) || "assumeTrue".equals(methodName)) {
constraints[checkedParam] = FALSE_VALUE;
return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(constraints, THROW_EXCEPTION));
}
if ("assertFalse".equals(methodName) || "assumeFalse".equals(methodName)) {
constraints[checkedParam] = TRUE_VALUE;
return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(constraints, THROW_EXCEPTION));
}
if ("assertNull".equals(methodName)) {
constraints[checkedParam] = NOT_NULL_VALUE;
return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(constraints, THROW_EXCEPTION));
}
if ("assertNotNull".equals(methodName) || "assumeNotNull".equals(methodName)) {
return failIfNull(checkedParam, paramCount);
@@ -239,7 +338,7 @@ public class HardcodedContracts {
private static List<MethodContract> failIfNull(int argIndex, int argCount) {
MethodContract.ValueConstraint[] constraints = createConstraintArray(argCount);
constraints[argIndex] = NULL_VALUE;
return Collections.singletonList(new MethodContract(constraints, THROW_EXCEPTION));
return Collections.singletonList(new StandardMethodContract(constraints, THROW_EXCEPTION));
}
public static boolean isHardcodedPure(PsiMethod method) {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* 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.
@@ -90,7 +90,7 @@ public class InferenceFromSourceUtil {
static boolean suppressNullable(PsiMethod method) {
if (method.getParameterList().getParametersCount() == 0) return false;
for (MethodContract contract : ControlFlowAnalyzer.getMethodContracts(method)) {
for (StandardMethodContract contract : ControlFlowAnalyzer.getMethodContracts(method)) {
if (contract.returnValue == MethodContract.ValueConstraint.NULL_VALUE) {
return true;
}
@@ -16,71 +16,45 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import java.util.Objects;
/**
* @author peter
* A method contract which states that method will have a concrete return value
* if arguments fulfill some constraint.
*
* @author Tagir Valeev
*/
public class MethodContract {
public final ValueConstraint[] arguments;
public final ValueConstraint returnValue;
public abstract class MethodContract {
// package private to avoid uncontrolled implementations
MethodContract() {
public MethodContract(@NotNull ValueConstraint[] arguments, @NotNull ValueConstraint returnValue) {
this.arguments = arguments;
this.returnValue = returnValue;
}
@NotNull
static ValueConstraint[] createConstraintArray(int paramCount) {
ValueConstraint[] args = new ValueConstraint[paramCount];
for (int i = 0; i < args.length; i++) {
args[i] = ValueConstraint.ANY_VALUE;
}
return args;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || o.getClass() != getClass()) return false;
MethodContract contract = (MethodContract)o;
if (!Arrays.equals(arguments, contract.arguments)) return false;
if (returnValue != contract.returnValue) return false;
return true;
}
@Override
public int hashCode() {
int result = 0;
for (ValueConstraint argument : arguments) {
result = 31 * result + argument.ordinal();
}
result = 31 * result + returnValue.ordinal();
return result;
}
@Override
public String toString() {
return StringUtil.join(arguments, constraint -> constraint.toString(), ", ") + " -> " + returnValue;
}
/**
* @return a value the method will return if the contract conditions fulfill
*/
public abstract ValueConstraint getReturnValue();
/**
* @return true if this contract result does not depend on arguments
*/
boolean isTrivial() {
return Arrays.stream(this.arguments).allMatch(Predicate.isEqual(ValueConstraint.ANY_VALUE));
return false;
}
abstract String getArgumentsPresentation();
abstract List<DfaValue> getConditions(DfaValueFactory factory, DfaValue qualifier, DfaValue[] arguments);
@Override
public String toString() {
return getArgumentsPresentation() + " -> " + getReturnValue();
}
public enum ValueConstraint {
@@ -102,62 +76,25 @@ public class MethodContract {
return this == NOT_NULL_VALUE || this == FALSE_VALUE;
}
/**
* Returns a condition value which should be applied to memory state to satisfy this constraint
*
* @param factory factory to create new values
* @param argValue argument value to test
* @return a condition
*/
public DfaValue getCondition(DfaValueFactory factory, DfaValue argValue) {
if (this == THROW_EXCEPTION || this == ANY_VALUE) {
return factory.getBoolean(true);
}
DfaConstValue expectedValue = Objects.requireNonNull(getComparisonValue(factory));
return factory.createCondition(argValue, RelationType.equivalence(!shouldUseNonEqComparison()), expectedValue);
}
@Override
public String toString() {
return myPresentableName;
}
}
public static List<MethodContract> parseContract(String text) throws ParseException {
List<MethodContract> result = ContainerUtil.newArrayList();
for (String clause : StringUtil.replace(text, " ", "").split(";")) {
String arrow = "->";
int arrowIndex = clause.indexOf(arrow);
if (arrowIndex < 0) {
throw new ParseException("A contract clause must be in form arg1, ..., argN -> return-value");
}
String beforeArrow = clause.substring(0, arrowIndex);
ValueConstraint[] args;
if (StringUtil.isNotEmpty(beforeArrow)) {
String[] argStrings = beforeArrow.split(",");
args = new ValueConstraint[argStrings.length];
for (int i = 0; i < args.length; i++) {
args[i] = parseConstraint(argStrings[i]);
}
} else {
args = new ValueConstraint[0];
}
result.add(new MethodContract(args, parseConstraint(clause.substring(arrowIndex + arrow.length()))));
}
return result;
}
private static ValueConstraint parseConstraint(String name) throws ParseException {
if (StringUtil.isEmpty(name)) throw new ParseException("Constraint should not be empty");
for (ValueConstraint constraint : ValueConstraint.values()) {
if (constraint.toString().equals(name)) return constraint;
}
throw new ParseException("Constraint should be one of: null, !null, true, false, fail, _. Found: " + name);
}
public static class ParseException extends Exception {
private ParseException(String message) {
super(message);
}
}
abstract static class QualifierBasedContract extends MethodContract {
public QualifierBasedContract(@NotNull ValueConstraint[] arguments,
@NotNull ValueConstraint returnValue) {
super(arguments, returnValue);
}
@Override
boolean isTrivial() {
return false;
}
abstract boolean applyContract(boolean matches, DfaValue qualifier, DfaMemoryState memoryState);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -90,7 +90,7 @@ internal object MethodDataExternalizer : DataExternalizer<Map<Int, MethodData>>
}
private fun readContract(input: DataInput): PreContract = when (input.readByte().toInt()) {
0 -> DelegationContract(readRange(input), input.readBoolean())
1 -> KnownContract(MethodContract(readContractArguments(input).toTypedArray(), readValueConstraint(input)))
1 -> KnownContract(StandardMethodContract(readContractArguments(input).toTypedArray(), readValueConstraint(input)))
2 -> MethodCallContract(readRange(input), readSeq(input) { readContractArguments(input) })
3 -> NegatingContract(readContract(input))
else -> SideEffectFilter(readRanges(input), readSeq(input) { readContract(input) })
@@ -16,10 +16,10 @@
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.dataFlow.MethodContract.QualifierBasedContract;
import com.intellij.codeInspection.dataFlow.instructions.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
@@ -33,16 +33,14 @@ import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.MultiMap;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ComparisonUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import gnu.trove.THashSet;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.intellij.psi.JavaTokenType.*;
/**
* @author peter
*/
@@ -84,6 +82,12 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue dfaSource = memState.pop();
DfaValue dfaDest = memState.pop();
if (instruction.getAssignedValue() != null) {
// It's possible that dfaDest on the stack is cleared to DfaTypeValue due to variable flush
// (e.g. during StateMerger#mergeByFacts), so we try to restore the original destination.
dfaDest = instruction.getAssignedValue();
}
if (dfaDest instanceof DfaVariableValue) {
DfaVariableValue var = (DfaVariableValue) dfaDest;
@@ -180,7 +184,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue dfaExpr = factory.createValue(instruction.getCasted());
if (dfaExpr != null) {
DfaTypeValue dfaType = (DfaTypeValue)factory.createTypeValue(instruction.getCastTo(), Nullness.UNKNOWN);
DfaRelationValue dfaInstanceof = factory.getRelationFactory().createRelation(dfaExpr, dfaType, INSTANCEOF_KEYWORD, false);
DfaRelationValue dfaInstanceof = factory.getRelationFactory().createRelation(dfaExpr, RelationType.IS, dfaType);
if (dfaInstanceof != null && !memState.applyInstanceofOrNull(dfaInstanceof)) {
onInstructionProducesCCE(instruction);
}
@@ -265,35 +269,34 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue[] argValues = popCallArguments(instruction, runner, memState, false);
DfaValue qualifier = popQualifier(instruction, runner, memState);
DfaValue result = null;
DfaValueFactory factory = runner.getFactory();
switch (methodName) {
case "of":
case "ofNullable":
case "fromNullable":
if ("of".equals(methodName) || (argValues != null && argValues.length == 1 && memState.isNotNull(argValues[0]))) {
result = runner.getFactory().getOptionalFactory().getOptional(true);
result = factory.getOptionalFactory().getOptional(true);
}
break;
case "empty":
case "absent":
result = runner.getFactory().getOptionalFactory().getOptional(false);
result = factory.getOptionalFactory().getOptional(false);
break;
case "orElse":
if (argValues != null && argValues.length == 1) {
switch (memState.checkOptional(qualifier)) {
case YES:
result = runner.getFactory().createTypeValue(instruction.getResultType(), Nullness.NOT_NULL);
break;
case NO:
result = argValues[0];
break;
case UNSURE:
DfaMemoryState falseState = memState.createCopy();
memState.push(runner.getFactory().createTypeValue(instruction.getResultType(), Nullness.NOT_NULL));
memState.applyIsPresentCheck(true, qualifier);
falseState.push(argValues[0]);
falseState.applyIsPresentCheck(false, qualifier);
return Arrays.asList(memState, falseState);
DfaMemoryState falseState = memState.createCopy();
DfaOptionalValue optional = factory.getOptionalFactory().getOptional(true);
DfaValue relation = factory.createCondition(qualifier, RelationType.IS, optional);
List<DfaMemoryState> states = new ArrayList<>(2);
if (memState.applyCondition(relation)) {
memState.push(factory.createTypeValue(instruction.getResultType(), Nullness.NOT_NULL));
states.add(memState);
}
if (falseState.applyCondition(relation.createNegated())) {
falseState.push(argValues[0]);
states.add(falseState);
}
return states;
}
break;
case "filter":
@@ -302,14 +305,17 @@ public class StandardInstructionVisitor extends InstructionVisitor {
case "map":
case "or":
case "orElseGet":
case "transform":
case "transform": {
DfaOptionalValue optional = factory.getOptionalFactory().getOptional(!methodName.startsWith("or"));
DfaValue relation = factory.createCondition(qualifier, RelationType.IS, optional);
for (DfaMemoryState closure : closures) {
closure.applyIsPresentCheck(!methodName.startsWith("or"), qualifier);
closure.applyCondition(relation);
}
break;
}
default:
}
memState.push(result == null ? getMethodResultValue(instruction, qualifier, runner.getFactory()) : result);
memState.push(result == null ? getMethodResultValue(instruction, qualifier, factory) : result);
return Collections.singletonList(memState);
}
@@ -377,69 +383,41 @@ public class StandardInstructionVisitor extends InstructionVisitor {
MethodCallInstruction instruction,
DfaValueFactory factory,
Set<DfaMemoryState> finalStates) {
DfaConstValue.Factory constFactory = factory.getConstFactory();
List<DfaValue> conditions = contract.getConditions(factory, qualifier, argValues);
if (StreamEx.of(conditions).allMatch(factory.getConstFactory().getTrue()::equals)) {
for (DfaMemoryState state : states) {
state.push(getDfaContractReturnValue(contract, instruction, factory));
finalStates.add(state);
}
return new LinkedHashSet<>();
}
if (StreamEx.of(conditions).has(factory.getConstFactory().getFalse())) {
return states;
}
LinkedHashSet<DfaMemoryState> falseStates = ContainerUtil.newLinkedHashSet();
for (int i = 0; i < argValues.length; i++) {
DfaValue argValue = argValues[i];
MethodContract.ValueConstraint constraint = contract.arguments[i];
DfaConstValue expectedValue = constraint.getComparisonValue(factory);
if (expectedValue == null) continue;
boolean nullContract = expectedValue == constFactory.getNull();
boolean invertCondition = constraint.shouldUseNonEqComparison();
DfaValue condition = factory.getRelationFactory().createRelation(argValue, expectedValue, EQEQ, invertCondition);
if (condition == null) {
if (!(argValue instanceof DfaConstValue)) {
for (DfaMemoryState state : states) {
DfaMemoryState falseCopy = state.createCopy();
if (nullContract) {
(invertCondition ? falseCopy : state).markEphemeral();
}
falseStates.add(falseCopy);
}
continue;
}
condition = constFactory.createFromValue((argValue == expectedValue) != invertCondition, PsiType.BOOLEAN, null);
}
LinkedHashSet<DfaMemoryState> nextStates = ContainerUtil.newLinkedHashSet();
for (DfaMemoryState state : states) {
boolean unknownVsNull = nullContract &&
argValue instanceof DfaVariableValue &&
((DfaMemoryStateImpl)state).getVariableState((DfaVariableValue)argValue).getNullability() == Nullness.UNKNOWN;
DfaMemoryState falseCopy = state.createCopy();
if (state.applyCondition(condition)) {
if (unknownVsNull && !invertCondition) {
state.markEphemeral();
}
nextStates.add(state);
}
if (falseCopy.applyCondition(condition.createNegated())) {
if (unknownVsNull && invertCondition) {
falseCopy.markEphemeral();
}
falseStates.add(falseCopy);
}
}
states = nextStates;
}
if (contract instanceof QualifierBasedContract) {
LinkedHashSet<DfaMemoryState> nextStates = ContainerUtil.newLinkedHashSet();
QualifierBasedContract qualifierBasedContract = (QualifierBasedContract)contract;
for (DfaMemoryState state : states) {
DfaMemoryState falseCopy = state.createCopy();
if (qualifierBasedContract.applyContract(true, qualifier, state)) {
nextStates.add(state);
}
if (qualifierBasedContract.applyContract(false, qualifier, falseCopy)) {
falseStates.add(falseCopy);
}
}
states = nextStates;
}
LinkedHashSet<DfaMemoryState> trueStates = ContainerUtil.newLinkedHashSet();
for (DfaMemoryState state : states) {
for (DfaValue condition : conditions) {
if (condition == null) {
condition = DfaUnknownValue.getInstance();
}
DfaMemoryState falseState = state.createCopy();
if (falseState.applyContractCondition(condition.createNegated())) {
falseStates.add(falseState);
}
if (!state.applyContractCondition(condition)) {
state = null;
break;
}
}
if(state != null) {
trueStates.add(state);
}
}
for (DfaMemoryState state : trueStates) {
state.push(getDfaContractReturnValue(contract, instruction, factory));
finalStates.add(state);
}
@@ -450,7 +428,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
private DfaValue getDfaContractReturnValue(MethodContract contract,
MethodCallInstruction instruction,
DfaValueFactory factory) {
switch (contract.returnValue) {
switch (contract.getReturnValue()) {
case NULL_VALUE: return factory.getConstFactory().getNull();
case NOT_NULL_VALUE: return factory.createTypeValue(instruction.getResultType(), Nullness.NOT_NULL);
case TRUE_VALUE: return factory.getConstFactory().getTrue();
@@ -529,7 +507,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
if (notNullable &&
problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter) {
DfaValueFactory factory = ((DfaMemoryStateImpl)state).getFactory();
state.applyCondition(factory.getRelationFactory().createRelation(value, factory.getConstFactory().getNull(), NE, false));
state.applyCondition(factory.createCondition(value, RelationType.NE, factory.getConstFactory().getNull()));
}
return notNullable;
}
@@ -542,27 +520,25 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DfaValue dfaLeft = memState.pop();
final IElementType opSign = instruction.getOperationSign();
if (ComparisonUtils.isComparisonOperation(opSign) || opSign == INSTANCEOF_KEYWORD) {
DfaInstructionState[] states = handleConstantComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
RelationType relationType = RelationType.fromElementType(opSign);
if (relationType != null) {
DfaInstructionState[] states = handleConstantComparison(instruction, runner, memState, dfaRight, dfaLeft, relationType);
if (states == null) {
states = handleRangeComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign);
}
if (states == null) {
states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft);
states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft, relationType);
}
if (states != null) {
return states;
}
}
DfaValue result = null;
if (AND == opSign) {
if (JavaTokenType.AND == opSign) {
LongRangeSet left = memState.getRange(dfaLeft);
LongRangeSet right = memState.getRange(dfaRight);
if(left != null && right != null) {
result = runner.getFactory().getRangeFactory().create(left.bitwiseAnd(right));
}
}
else if (PLUS == opSign) {
else if (JavaTokenType.PLUS == opSign) {
result = instruction.getNonNullStringValue(runner.getFactory());
}
else {
@@ -582,20 +558,20 @@ public class StandardInstructionVisitor extends InstructionVisitor {
private DfaInstructionState[] handleRelationBinop(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight, DfaValue dfaLeft) {
DfaValue dfaRight,
DfaValue dfaLeft,
RelationType relationType) {
DfaValueFactory factory = runner.getFactory();
final Instruction next = runner.getInstruction(instruction.getIndex() + 1);
DfaRelationValue dfaRelation = factory.getRelationFactory().createRelation(dfaLeft, dfaRight, instruction.getOperationSign(), false);
if (dfaRelation == null) {
return null;
}
DfaValue condition = factory.createCondition(dfaLeft, relationType, dfaRight);
if (condition instanceof DfaUnknownValue) return null;
myCanBeNullInInstanceof.add(instruction);
ArrayList<DfaInstructionState> states = new ArrayList<>();
ArrayList<DfaInstructionState> states = new ArrayList<>(2);
final DfaMemoryState trueCopy = memState.createCopy();
if (trueCopy.applyCondition(dfaRelation)) {
if (trueCopy.applyCondition(condition)) {
trueCopy.push(factory.getConstFactory().getTrue());
instruction.setTrueReachable();
states.add(new DfaInstructionState(next, trueCopy));
@@ -603,7 +579,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
//noinspection UnnecessaryLocalVariable
DfaMemoryState falseCopy = memState;
if (falseCopy.applyCondition(dfaRelation.createNegated())) {
if (falseCopy.applyCondition(condition.createNegated())) {
falseCopy.push(factory.getConstFactory().getFalse());
instruction.setFalseReachable();
states.add(new DfaInstructionState(next, falseCopy));
@@ -632,45 +608,24 @@ public class StandardInstructionVisitor extends InstructionVisitor {
myUsefulInstanceofs.add(instruction);
}
@Nullable
private static DfaInstructionState[] handleRangeComparison(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState state,
DfaValue right,
DfaValue left, IElementType sign) {
LongRangeSet leftRange = state.getRange(left);
if (leftRange == null) return null;
LongRangeSet rightRange = state.getRange(right);
if (rightRange == null) return null;
LongRangeSet constraint = rightRange.fromRelation(sign);
if (constraint != null && !constraint.intersects(leftRange)) {
return alwaysFalse(instruction, runner, state);
}
LongRangeSet revConstraint = rightRange.fromRelation(ComparisonUtils.getNegatedComparisonTokenType(sign));
if (revConstraint != null && !revConstraint.intersects(leftRange)) {
return alwaysTrue(instruction, runner, state);
}
return null;
}
@Nullable
private static DfaInstructionState[] handleConstantComparison(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue dfaRight,
DfaValue dfaLeft, IElementType opSign) {
DfaValue dfaLeft, RelationType relationType) {
if (dfaLeft instanceof DfaVariableValue && dfaRight instanceof DfaVariableValue) {
Number leftValue = getKnownNumberValue(memState, (DfaVariableValue)dfaLeft);
Number rightValue = getKnownNumberValue(memState, (DfaVariableValue)dfaRight);
if (leftValue != null && rightValue != null) {
return checkComparisonWithKnownValue(instruction, runner, memState, opSign, leftValue, rightValue);
return checkComparisonWithKnownValue(instruction, runner, memState, relationType, leftValue, rightValue);
}
}
if (dfaRight instanceof DfaConstValue && dfaLeft instanceof DfaVariableValue) {
Object value = ((DfaConstValue)dfaRight).getValue();
if (value instanceof Number) {
DfaInstructionState[] result = checkComparingWithConstant(instruction, runner, memState, (DfaVariableValue)dfaLeft, opSign,
DfaInstructionState[] result = checkComparingWithConstant(instruction, runner, memState, (DfaVariableValue)dfaLeft, relationType,
(Number)value);
if (result != null) {
return result;
@@ -678,17 +633,17 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
if (dfaRight instanceof DfaVariableValue && dfaLeft instanceof DfaConstValue) {
return handleConstantComparison(instruction, runner, memState, dfaLeft, dfaRight, DfaRelationValue.getSymmetricOperation(opSign));
return handleConstantComparison(instruction, runner, memState, dfaLeft, dfaRight, relationType.getFlipped());
}
if (EQEQ != opSign && NE != opSign) {
if (relationType != RelationType.EQ && relationType != RelationType.NE) {
return null;
}
if (dfaLeft instanceof DfaConstValue && dfaRight instanceof DfaConstValue ||
dfaLeft == runner.getFactory().getConstFactory().getContractFail() ||
dfaRight == runner.getFactory().getConstFactory().getContractFail()) {
boolean negated = (NE == opSign) ^ (DfaMemoryStateImpl.isNaN(dfaLeft) || DfaMemoryStateImpl.isNaN(dfaRight));
boolean negated = (relationType == RelationType.NE) ^ (DfaMemoryStateImpl.isNaN(dfaLeft) || DfaMemoryStateImpl.isNaN(dfaRight));
if (dfaLeft == dfaRight ^ negated) {
return alwaysTrue(instruction, runner, memState);
}
@@ -703,7 +658,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
DataFlowRunner runner,
DfaMemoryState memState,
DfaVariableValue var,
IElementType opSign, Number comparedWith) {
RelationType opSign, Number comparedWith) {
Number knownValue = getKnownNumberValue(memState, var);
if (knownValue != null) {
return checkComparisonWithKnownValue(instruction, runner, memState, opSign, knownValue, comparedWith);
@@ -720,26 +675,27 @@ public class StandardInstructionVisitor extends InstructionVisitor {
private static DfaInstructionState[] checkComparisonWithKnownValue(BinopInstruction instruction,
DataFlowRunner runner,
DfaMemoryState memState,
IElementType opSign,
RelationType opSign,
Number leftValue,
Number rightValue) {
int cmp = compare(leftValue, rightValue);
Boolean result = null;
boolean hasNaN = DfaUtil.isNaN(leftValue) || DfaUtil.isNaN(rightValue);
if (cmp < 0 || cmp > 0) {
if(opSign == EQEQ) result = false;
else if (opSign == NE) result = true;
if(opSign == RelationType.EQ) result = false;
else if (opSign == RelationType.NE) result = true;
}
if (opSign == LT) {
result = cmp < 0;
if (opSign == RelationType.LT) {
result = !hasNaN && cmp < 0;
}
else if (opSign == GT) {
result = cmp > 0;
else if (opSign == RelationType.GT) {
result = !hasNaN && cmp > 0;
}
else if (opSign == LE) {
result = cmp <= 0;
else if (opSign == RelationType.LE) {
result = !hasNaN && cmp <= 0;
}
else if (opSign == GE) {
result = cmp >= 0;
else if (opSign == RelationType.GE) {
result = !hasNaN && cmp >= 0;
}
if (result == null) {
return null;
@@ -783,5 +739,4 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
return false;
}
}
@@ -0,0 +1,134 @@
/*
* 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.dataFlow;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
/**
* A method contract which is described by {@link MethodContract.ValueConstraint} constraints on arguments.
* Such contract can be created from {@link org.jetbrains.annotations.Contract} annotation.
*
* @author peter
*/
public final class StandardMethodContract extends MethodContract {
public final ValueConstraint[] arguments;
public final ValueConstraint returnValue;
public StandardMethodContract(@NotNull ValueConstraint[] arguments, @NotNull ValueConstraint returnValue) {
this.arguments = arguments;
this.returnValue = returnValue;
}
@Override
public ValueConstraint getReturnValue() {
return returnValue;
}
@NotNull
static ValueConstraint[] createConstraintArray(int paramCount) {
ValueConstraint[] args = new ValueConstraint[paramCount];
for (int i = 0; i < args.length; i++) {
args[i] = ValueConstraint.ANY_VALUE;
}
return args;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || o.getClass() != getClass()) return false;
StandardMethodContract contract = (StandardMethodContract)o;
if (!Arrays.equals(arguments, contract.arguments)) return false;
if (returnValue != contract.returnValue) return false;
return true;
}
@Override
public int hashCode() {
int result = 0;
for (ValueConstraint argument : arguments) {
result = 31 * result + argument.ordinal();
}
result = 31 * result + returnValue.ordinal();
return result;
}
boolean isTrivial() {
return Arrays.stream(this.arguments).allMatch(Predicate.isEqual(ValueConstraint.ANY_VALUE));
}
@Override
String getArgumentsPresentation() {
return StringUtil.join(arguments, constraint -> constraint.toString(), ", ");
}
@Override
List<DfaValue> getConditions(DfaValueFactory factory, DfaValue qualifier, DfaValue[] argValues) {
return StreamEx.zip(arguments, argValues, (constraint, value) -> constraint.getCondition(factory, value))
.without(factory.getConstFactory().getTrue()).toList();
}
public static List<StandardMethodContract> parseContract(String text) throws ParseException {
List<StandardMethodContract> result = ContainerUtil.newArrayList();
for (String clause : StringUtil.replace(text, " ", "").split(";")) {
String arrow = "->";
int arrowIndex = clause.indexOf(arrow);
if (arrowIndex < 0) {
throw new ParseException("A contract clause must be in form arg1, ..., argN -> return-value");
}
String beforeArrow = clause.substring(0, arrowIndex);
ValueConstraint[] args;
if (StringUtil.isNotEmpty(beforeArrow)) {
String[] argStrings = beforeArrow.split(",");
args = new ValueConstraint[argStrings.length];
for (int i = 0; i < args.length; i++) {
args[i] = parseConstraint(argStrings[i]);
}
} else {
args = new ValueConstraint[0];
}
result.add(new StandardMethodContract(args, parseConstraint(clause.substring(arrowIndex + arrow.length()))));
}
return result;
}
private static ValueConstraint parseConstraint(String name) throws ParseException {
if (StringUtil.isEmpty(name)) throw new ParseException("Constraint should not be empty");
for (ValueConstraint constraint : ValueConstraint.values()) {
if (constraint.toString().equals(name)) return constraint;
}
throw new ParseException("Constraint should be one of: null, !null, true, false, fail, _. Found: " + name);
}
public static class ParseException extends Exception {
private ParseException(String message) {
super(message);
}
}
}
@@ -17,9 +17,9 @@ package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.UnorderedPair;
import com.intellij.psi.JavaTokenType;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
@@ -119,7 +119,7 @@ class StateMerger {
if (inequalitiesToRestore != null) {
DfaRelationValue.Factory relationFactory = state.getFactory().getRelationFactory();
for (DfaConstValue toRestore : inequalitiesToRestore) {
state.applyCondition(relationFactory.createRelation(removedFact.myVar, toRestore, JavaTokenType.EQEQ, true));
state.applyCondition(relationFactory.createRelation(removedFact.myVar, RelationType.NE, toRestore));
}
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2009 JetBrains s.r.o.
* 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.
@@ -71,9 +71,9 @@ public class MethodCallInstruction extends Instruction {
myArgRequiredNullability = Collections.emptyMap();
}
public MethodCallInstruction(@NotNull PsiCall call, @Nullable DfaValue precalculatedReturnValue, List<MethodContract> contracts) {
public MethodCallInstruction(@NotNull PsiCall call, @Nullable DfaValue precalculatedReturnValue, List<? extends MethodContract> contracts) {
myContext = call;
myContracts = contracts;
myContracts = Collections.unmodifiableList(contracts);
myMethodType = MethodType.REGULAR_METHOD_CALL;
myCall = call;
final PsiExpressionList argList = call.getArgumentList();
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
* 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.
@@ -27,18 +27,18 @@ import com.siyeh.ig.psiutils.SideEffectChecker
* @author peter
*/
interface PreContract {
fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract>
fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<StandardMethodContract>
fun negate(): PreContract? = NegatingContract(this)
}
internal data class KnownContract(val contract: MethodContract) : PreContract {
internal data class KnownContract(val contract: StandardMethodContract) : PreContract {
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock) = listOf(contract)
override fun negate() = negateContract(contract)?.let(::KnownContract)
}
internal data class DelegationContract(internal val expression: ExpressionRange, internal val negated: Boolean) : PreContract {
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract> {
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<StandardMethodContract> {
val call = expression.restoreExpression(body()) as PsiMethodCallExpression? ?: return emptyList()
val result = call.resolveMethodGenerics()
@@ -52,7 +52,7 @@ internal data class DelegationContract(internal val expression: ExpressionRange,
convertDelegatedMethodContract(method, parameters, arguments, varArgCall, dc)
}
if (NullableNotNullManager.isNotNull(targetMethod)) {
return fromDelegate.map { returnNotNull(it) } + listOf(MethodContract(emptyConstraints(method), NOT_NULL_VALUE))
return fromDelegate.map { returnNotNull(it) } + listOf(StandardMethodContract(emptyConstraints(method), NOT_NULL_VALUE))
}
return fromDelegate
}
@@ -61,7 +61,7 @@ internal data class DelegationContract(internal val expression: ExpressionRange,
targetParameters: Array<PsiParameter>,
callArguments: Array<PsiExpression>,
varArgCall: Boolean,
targetContract: MethodContract): MethodContract? {
targetContract: StandardMethodContract): StandardMethodContract? {
var answer: Array<MethodContract.ValueConstraint>? = emptyConstraints(callerMethod)
for (i in targetContract.arguments.indices) {
if (i >= callArguments.size) return null
@@ -85,12 +85,13 @@ internal data class DelegationContract(internal val expression: ExpressionRange,
}
}
val returnValue = if (negated) negateConstraint(targetContract.returnValue) else targetContract.returnValue
return answer?.let { MethodContract(it, returnValue) }
return answer?.let { StandardMethodContract(it, returnValue) }
}
private fun emptyConstraints(method: PsiMethod) = MethodContract.createConstraintArray(method.parameterList.parametersCount)
private fun emptyConstraints(method: PsiMethod) = StandardMethodContract.createConstraintArray(method.parameterList.parametersCount)
private fun returnNotNull(mc: MethodContract) = if (mc.returnValue == THROW_EXCEPTION) mc else MethodContract(mc.arguments, NOT_NULL_VALUE)
private fun returnNotNull(mc: StandardMethodContract) = if (mc.returnValue == THROW_EXCEPTION) mc else StandardMethodContract(
mc.arguments, NOT_NULL_VALUE)
private fun getLiteralConstraint(argument: PsiExpression) = when (argument) {
is PsiLiteralExpression -> ContractInferenceInterpreter.getLiteralConstraint(argument.getFirstChild().node.elementType)
@@ -105,7 +106,7 @@ internal data class DelegationContract(internal val expression: ExpressionRange,
internal data class SideEffectFilter(internal val expressionsToCheck: List<ExpressionRange>, internal val contracts: List<PreContract>) : PreContract {
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract> {
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<StandardMethodContract> {
if (expressionsToCheck.any { d -> mayHaveSideEffects(body(), d) }) {
return emptyList()
}
@@ -120,16 +121,16 @@ internal data class NegatingContract(internal val negated: PreContract) : PreCon
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock) = negated.toContracts(method, body).mapNotNull(::negateContract)
}
private fun negateContract(c: MethodContract): MethodContract? {
private fun negateContract(c: StandardMethodContract): StandardMethodContract? {
val ret = c.returnValue
return if (ret == TRUE_VALUE || ret == FALSE_VALUE) MethodContract(c.arguments, negateConstraint(ret)) else null
return if (ret == TRUE_VALUE || ret == FALSE_VALUE) StandardMethodContract(c.arguments, negateConstraint(ret)) else null
}
@Suppress("EqualsOrHashCode")
internal data class MethodCallContract(internal val call: ExpressionRange, internal val states: List<List<MethodContract.ValueConstraint>>) : PreContract {
override fun hashCode() = call.hashCode() * 31 + states.flatten().map { it.ordinal }.hashCode()
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<MethodContract> {
override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List<StandardMethodContract> {
val target = (call.restoreExpression(body()) as PsiMethodCallExpression?)?.resolveMethod()
if (target != null && NullableNotNullManager.isNotNull(target)) {
return ContractInferenceInterpreter.toContracts(states.map { it.toTypedArray() }, NOT_NULL_VALUE)
@@ -15,10 +15,12 @@
*/
package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.psi.JavaTokenType;
import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
import com.intellij.codeInspection.dataFlow.value.DfaRangeValue;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ThreeState;
import org.jetbrains.annotations.Nullable;
@@ -120,31 +122,31 @@ public abstract class LongRangeSet {
* @param relation relation to be applied to current set (JavaTokenType.EQEQ/NE/GT/GE/LT/LE)
* @return new set or null if relation is unsupported
*/
public LongRangeSet fromRelation(IElementType relation) {
if (isEmpty()) return null;
if (JavaTokenType.EQEQ.equals(relation)) {
return this;
public LongRangeSet fromRelation(@Nullable DfaRelationValue.RelationType relation) {
if (isEmpty() || relation == null) return null;
switch (relation) {
case EQ:
return this;
case NE: {
long min = min();
if (min == max()) return all().without(min);
return all();
}
case GT: {
long min = min();
return min == Long.MAX_VALUE ? empty() : range(min + 1, Long.MAX_VALUE);
}
case GE:
return range(min(), Long.MAX_VALUE);
case LE:
return range(Long.MIN_VALUE, max());
case LT: {
long max = max();
return max == Long.MIN_VALUE ? empty() : range(Long.MIN_VALUE, max - 1);
}
default:
return null;
}
if (JavaTokenType.NE.equals(relation)) {
long min = min();
if (min == max()) return all().without(min);
return all();
}
if (JavaTokenType.GT.equals(relation)) {
long min = min();
return min == Long.MAX_VALUE ? empty() : range(min + 1, Long.MAX_VALUE);
}
if (JavaTokenType.GE.equals(relation)) {
return range(min(), Long.MAX_VALUE);
}
if (JavaTokenType.LE.equals(relation)) {
return range(Long.MIN_VALUE, max());
}
if (JavaTokenType.LT.equals(relation)) {
long max = max();
return max == Long.MIN_VALUE ? empty() : range(Long.MIN_VALUE, max - 1);
}
return null;
}
/**
@@ -326,6 +328,17 @@ public abstract class LongRangeSet {
return null;
}
@Nullable
public static LongRangeSet fromDfaValue(DfaValue value) {
if (value instanceof DfaRangeValue) {
return ((DfaRangeValue)value).getValue();
}
if (value instanceof DfaConstValue) {
return fromConstant(((DfaConstValue)value).getValue());
}
return null;
}
/**
* Creates a new set which contains all the numbers between from (inclusive) and to (inclusive)
*
@@ -42,6 +42,11 @@ public class DfaRangeValue extends DfaValue {
return myValue;
}
@Override
public String toString() {
return myValue.toString();
}
public static class Factory {
private Map<LongRangeSet, DfaRangeValue> myValues = new HashMap<>();
private DfaValueFactory myFactory;
@@ -24,117 +24,130 @@
*/
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Trinity;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import static com.intellij.psi.JavaTokenType.*;
import java.util.Map;
public class DfaRelationValue extends DfaValue {
@Override
public DfaRelationValue createNegated() {
return myFactory.getRelationFactory().createCanonicalRelation(myLeftOperand, myRelation.getNegated(), myRightOperand);
}
private DfaValue myLeftOperand;
private DfaValue myRightOperand;
private IElementType myRelation;
private boolean myIsNegated;
private RelationType myRelation;
public static class Factory {
private final DfaRelationValue mySharedInstance;
private final HashMap<String,ArrayList<DfaRelationValue>> myStringToObject;
private final DfaValueFactory myFactory;
public enum RelationType {
LE("<="), LT("<"), GE(">="), GT(">"), EQ("=="), NE("!="),
/**
* Value on the left belongs to the class of values defined on the right.
* Currently used to represent:
* - instanceof (DfaValue IS DfaTypeValue)
* - optional presense (DfaValue IS DfaOptionalValue)
*/
IS("is"),
/**
* Value on the left does not belong to the class of values defined on the right (opposite to IS).
*/
IS_NOT("isn't");
Factory(DfaValueFactory factory) {
myFactory = factory;
mySharedInstance = new DfaRelationValue(factory);
myStringToObject = new HashMap<>();
private final String myName;
RelationType(String name) {
myName = name;
}
public DfaRelationValue createRelation(DfaValue dfaLeft, DfaValue dfaRight, IElementType relation, boolean negated) {
if (PLUS == relation) return null;
@NotNull
public RelationType getNegated() {
switch (this) {
case LE:
return GT;
case LT:
return GE;
case GE:
return LT;
case GT:
return LE;
case EQ:
return NE;
case NE:
return EQ;
case IS:
return IS_NOT;
case IS_NOT:
return IS;
}
throw new InternalError("Unexpected enum value: " + this);
}
if (dfaLeft instanceof DfaVariableValue || dfaLeft instanceof DfaBoxedValue || dfaLeft instanceof DfaUnboxedValue
|| dfaRight instanceof DfaVariableValue || dfaRight instanceof DfaBoxedValue || dfaRight instanceof DfaUnboxedValue) {
if (!(dfaLeft instanceof DfaVariableValue || dfaLeft instanceof DfaBoxedValue || dfaLeft instanceof DfaUnboxedValue)) {
return createRelation(dfaRight, dfaLeft, getSymmetricOperation(relation), negated);
}
return createCanonicalRelation(relation, negated, dfaLeft, dfaRight);
}
if (dfaLeft instanceof DfaTypeValue && ((DfaTypeValue)dfaLeft).isNotNull() && dfaRight instanceof DfaConstValue) {
return createCanonicalRelation(relation, negated, dfaLeft, dfaRight);
}
else if (dfaRight instanceof DfaTypeValue && ((DfaTypeValue)dfaRight).isNotNull() && dfaLeft instanceof DfaConstValue) {
return createCanonicalRelation(relation, negated, dfaRight, dfaLeft);
}
else {
return null;
@Nullable
public RelationType getFlipped() {
switch (this) {
case LE:
return GE;
case LT:
return GT;
case GE:
return LE;
case GT:
return LT;
case EQ:
case NE:
return this;
default:
return null;
}
}
private DfaRelationValue createCanonicalRelation(IElementType relation,
boolean negated,
@NotNull final DfaValue dfaLeft,
@NotNull final DfaValue dfaRight) {
// To canonical form.
if (NE == relation) {
relation = EQEQ;
negated = !negated;
}
else if (LT == relation) {
relation = GE;
negated = !negated;
}
else if (LE == relation) {
relation = GT;
negated = !negated;
}
mySharedInstance.myLeftOperand = dfaLeft;
mySharedInstance.myRightOperand = dfaRight;
mySharedInstance.myRelation = relation;
mySharedInstance.myIsNegated = negated;
String id = mySharedInstance.toString();
ArrayList<DfaRelationValue> conditions = myStringToObject.get(id);
if (conditions == null) {
conditions = new ArrayList<>();
myStringToObject.put(id, conditions);
}
else {
for (DfaRelationValue rel : conditions) {
if (rel.hardEquals(mySharedInstance)) return rel;
}
}
DfaRelationValue result = new DfaRelationValue(dfaLeft, dfaRight, relation, negated, myFactory);
conditions.add(result);
return result;
@Override
public String toString() {
return myName;
}
@Nullable
public static RelationType fromElementType(IElementType type) {
if(JavaTokenType.EQEQ.equals(type)) {
return EQ;
}
if(JavaTokenType.NE.equals(type)) {
return NE;
}
if(JavaTokenType.LT.equals(type)) {
return LT;
}
if(JavaTokenType.GT.equals(type)) {
return GT;
}
if(JavaTokenType.LE.equals(type)) {
return LE;
}
if(JavaTokenType.GE.equals(type)) {
return GE;
}
if(JavaTokenType.INSTANCEOF_KEYWORD.equals(type)) {
return IS;
}
return null;
}
public static RelationType equivalence(boolean equal) {
return equal ? EQ : NE;
}
}
public static IElementType getSymmetricOperation(IElementType sign) {
if (LT == sign) return GT;
if (GE == sign) return LE;
if (GT == sign) return LT;
if (LE == sign) return GE;
return sign;
}
private DfaRelationValue(DfaValueFactory factory) {
super(factory);
}
private DfaRelationValue(DfaValue myLeftOperand, DfaValue myRightOperand, IElementType myRelation, boolean myIsNegated,
private DfaRelationValue(DfaValue leftOperand, DfaValue rightOperand, RelationType relationType,
DfaValueFactory factory) {
super(factory);
this.myLeftOperand = myLeftOperand;
this.myRightOperand = myRightOperand;
this.myRelation = myRelation;
this.myIsNegated = myIsNegated;
this.myLeftOperand = leftOperand;
this.myRightOperand = rightOperand;
this.myRelation = relationType;
}
public DfaValue getLeftOperand() {
@@ -145,52 +158,59 @@ public class DfaRelationValue extends DfaValue {
return myRightOperand;
}
public boolean isNegated() {
return myIsNegated;
}
public static class Factory {
private final Map<Trinity<DfaValue, DfaValue, RelationType>, DfaRelationValue> myValues;
private final DfaValueFactory myFactory;
@Override
public DfaValue createNegated() {
return myFactory.getRelationFactory().createRelation(myLeftOperand, myRightOperand, myRelation, !myIsNegated);
}
Factory(DfaValueFactory factory) {
myFactory = factory;
myValues = new HashMap<>();
}
private boolean hardEquals(DfaRelationValue rel) {
return Comparing.equal(rel.myLeftOperand,myLeftOperand)
&& Comparing.equal(rel.myRightOperand,myRightOperand) &&
rel.myRelation == myRelation &&
rel.myIsNegated == myIsNegated;
public DfaRelationValue createRelation(DfaValue dfaLeft, RelationType relationType, DfaValue dfaRight) {
if ((relationType == RelationType.IS || relationType == RelationType.IS_NOT) && dfaRight instanceof DfaOptionalValue) {
return createCanonicalRelation(dfaLeft, relationType, dfaRight);
}
if (dfaLeft instanceof DfaVariableValue || dfaLeft instanceof DfaBoxedValue || dfaLeft instanceof DfaUnboxedValue
|| dfaRight instanceof DfaVariableValue || dfaRight instanceof DfaBoxedValue || dfaRight instanceof DfaUnboxedValue) {
if (!(dfaLeft instanceof DfaVariableValue || dfaLeft instanceof DfaBoxedValue || dfaLeft instanceof DfaUnboxedValue)) {
RelationType flipped = relationType.getFlipped();
return flipped == null ? null : createCanonicalRelation(dfaRight, flipped, dfaLeft);
}
return createCanonicalRelation(dfaLeft, relationType, dfaRight);
}
if (dfaLeft instanceof DfaTypeValue && dfaRight instanceof DfaConstValue) {
return createCanonicalRelation(DfaUnknownValue.getInstance(), relationType, dfaRight);
}
else if (dfaRight instanceof DfaTypeValue && dfaLeft instanceof DfaConstValue) {
return createCanonicalRelation(DfaUnknownValue.getInstance(), relationType, dfaLeft);
}
return null;
}
@NotNull
private DfaRelationValue createCanonicalRelation(@NotNull final DfaValue dfaLeft,
@NotNull RelationType relationType,
@NotNull final DfaValue dfaRight) {
return myValues.computeIfAbsent(Trinity.create(dfaLeft, dfaRight, relationType),
k -> new DfaRelationValue(dfaLeft, dfaRight, relationType, myFactory));
}
}
public boolean isEquality() {
return myRelation == EQEQ && !myIsNegated;
return myRelation == RelationType.EQ;
}
public boolean isNonEquality() {
return myRelation == EQEQ && myIsNegated || myRelation == GT && !myIsNegated || myRelation == GE && myIsNegated;
return myRelation == RelationType.NE || myRelation == RelationType.GT || myRelation == RelationType.LT;
}
/**
* @return comparison operation (GT, GE, LE, LT, EQEQ, NE) if this relation represents comparison, null otherwise
*/
@Nullable
public IElementType getComparisonOperation() {
if(myRelation == GT) {
return myIsNegated ? LE : GT;
}
if(myRelation == GE) {
return myIsNegated ? LT : GE;
}
if(myRelation == EQEQ) {
return myIsNegated ? NE : EQEQ;
}
return null;
}
public boolean isInstanceOf() {
return myRelation == INSTANCEOF_KEYWORD;
@NotNull
public RelationType getRelation() {
return myRelation;
}
@NonNls public String toString() {
return (isNegated() ? "not " : "") + myLeftOperand + " " + myRelation + " " + myRightOperand;
return myLeftOperand + " " + myRelation + " " + myRightOperand;
}
}
@@ -24,13 +24,13 @@
*/
package com.intellij.codeInspection.dataFlow.value;
import com.intellij.codeInspection.dataFlow.DfaControlTransferValue;
import com.intellij.codeInspection.dataFlow.Nullness;
import com.intellij.codeInspection.dataFlow.TransferTarget;
import com.intellij.codeInspection.dataFlow.Trap;
import com.intellij.codeInspection.dataFlow.*;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FList;
@@ -105,6 +105,93 @@ public class DfaValueFactory {
return getConstFactory().create(literal);
}
@NotNull
public DfaValue createStringLength(DfaValue value) {
if (value instanceof DfaVariableValue) {
DfaVariableValue variableValue = (DfaVariableValue)value;
PsiType type = variableValue.getVariableType();
if (type != null && type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(type);
if (psiClass != null) {
PsiMethod[] lengthMethods = psiClass.findMethodsByName("length", false);
if (lengthMethods.length == 1) {
return getVarFactory().createVariableValue(lengthMethods[0], PsiType.INT, false, variableValue);
}
}
}
}
if(value instanceof DfaConstValue) {
Object str = ((DfaConstValue)value).getValue();
if(str instanceof String) {
return getConstFactory().createFromValue(((String)str).length(), PsiType.INT, null);
}
}
return DfaUnknownValue.getInstance();
}
/**
* Create condition (suitable to pass into {@link DfaMemoryState#applyCondition(DfaValue)}),
* evaluating it statically if possible.
*
* @param dfaLeft left operand
* @param relationType relation
* @param dfaRight right operand
* @return resulting condition: either {@link DfaRelationValue} or {@link DfaConstValue} (true or false) or {@link DfaUnknownValue}.
*/
@NotNull
public DfaValue createCondition(DfaValue dfaLeft, RelationType relationType, DfaValue dfaRight) {
DfaConstValue value = tryEvaluate(dfaLeft, relationType, dfaRight);
if (value != null) return value;
DfaRelationValue relation = getRelationFactory().createRelation(dfaLeft, relationType, dfaRight);
if (relation != null) return relation;
return DfaUnknownValue.getInstance();
}
@Nullable
private DfaConstValue tryEvaluate(DfaValue dfaLeft, RelationType relationType, DfaValue dfaRight) {
if(dfaRight instanceof DfaTypeValue && dfaLeft == getConstFactory().getNull()) {
return tryEvaluate(dfaRight, relationType, dfaLeft);
}
if (dfaLeft instanceof DfaTypeValue && dfaRight == getConstFactory().getNull() && ((DfaTypeValue)dfaLeft).isNotNull()) {
if (relationType == RelationType.EQ) {
return getConstFactory().getFalse();
}
if (relationType == RelationType.NE) {
return getConstFactory().getTrue();
}
}
if(dfaLeft instanceof DfaOptionalValue && dfaRight instanceof DfaOptionalValue) {
if(relationType == RelationType.IS) {
return getBoolean(dfaLeft == dfaRight);
} else if(relationType == RelationType.IS_NOT) {
return getBoolean(dfaLeft != dfaRight);
}
}
LongRangeSet leftRange = LongRangeSet.fromDfaValue(dfaLeft);
LongRangeSet rightRange = LongRangeSet.fromDfaValue(dfaRight);
if (leftRange != null && rightRange != null) {
LongRangeSet constraint = rightRange.fromRelation(relationType);
if (constraint != null && !constraint.intersects(leftRange)) {
return getConstFactory().getFalse();
}
LongRangeSet revConstraint = rightRange.fromRelation(relationType.getNegated());
if (revConstraint != null && !revConstraint.intersects(leftRange)) {
return getConstFactory().getTrue();
}
}
if(dfaLeft instanceof DfaConstValue && dfaRight instanceof DfaConstValue &&
(relationType == RelationType.EQ || relationType == RelationType.NE)) {
return getBoolean(dfaLeft == dfaRight ^
!DfaUtil.isNaN(((DfaConstValue)dfaLeft).getValue()) ^
relationType == RelationType.EQ);
}
return null;
}
public DfaConstValue getBoolean(boolean value) {
return value ? getConstFactory().getTrue() : getConstFactory().getFalse();
}
@@ -273,10 +273,10 @@ public class DfaVariableValue extends DfaValue {
public boolean isFlushableByCalls() {
if (myVariable instanceof PsiLocalVariable || myVariable instanceof PsiParameter) return false;
if (myVariable instanceof PsiVariable && myVariable.hasModifierProperty(PsiModifier.FINAL)) {
if (myVariable instanceof PsiVariable && myVariable.hasModifierProperty(PsiModifier.FINAL) ||
myVariable instanceof PsiMethod && MethodUtils.isStringLength((PsiMethod)myVariable)) {
return myQualifier != null && myQualifier.isFlushableByCalls();
}
if (myVariable instanceof PsiMethod && MethodUtils.isStringLength((PsiMethod)myVariable)) return false;
return true;
}
@@ -7,8 +7,8 @@ class CandidateInfo<T> {
}
static void test() {
new <warning descr="The call to CandidateInfo always fails, according to its method contracts">CandidateInfo</warning>(null, true, false);
new <warning descr="The call to CandidateInfo always fails, according to its method contracts">CandidateInfo</warning>(null, true, true);
new <warning descr="The call to 'CandidateInfo' always fails, according to its method contracts">CandidateInfo</warning>(null, true, false);
new <warning descr="The call to 'CandidateInfo' always fails, according to its method contracts">CandidateInfo</warning>(null, true, true);
new CandidateInfo(null, false, true);
new CandidateInfo(new Object(), true, true);
@@ -0,0 +1,30 @@
public class CustomContracts {
public void testSubstring(String s) {
if (s.<warning descr="The call to 'substring' always fails, according to its method contracts">substring</warning>(-1).length() == 0) {
System.out.println("Oops");
}
}
public void testSubstring2(String s, int index) {
if (s.substring(0, index).length() == 0 || <warning descr="Condition 'index < 0' is always 'false' when reached">index < 0</warning>) {
System.out.println("Oops");
}
}
public void testSubstring3(String s, int index) {
if (s.substring(3, index).equals("foo") || <warning descr="Condition 'index == 1' is always 'false' when reached">index == 1</warning>) {
System.out.println("Oops");
}
}
public void testCharAt(String s) {
int index = 0;
while (Character.isDigit(s.charAt(index))) {
index++;
}
if (index == 0 || <warning descr="Condition 'index == s.length()' is always 'false' when reached">index == s.length()</warning>) {
System.out.println("Wrong");
}
}
}
@@ -0,0 +1,9 @@
public class DoubleNaN {
void test() {
double x = Double.NaN;
double y = Double.NaN;
if(<warning descr="Condition 'x >= y' is always 'false'">x >= y</warning>) {
System.out.println("oops");
}
}
}
@@ -0,0 +1,45 @@
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
public class FlushVariableOnStackToNotNullType {
private String str;
@Contract(value = "null -> true", pure = true)
public static boolean isEmpty(@Nullable String s) {
return s == null || s.equals("");
}
interface State {
@Nullable
String get() throws IOException;
}
@Nullable
public String getMessage(State currentState, boolean x, @NotNull String message) {
String errorMessage = null;
if (x) {
errorMessage = message;
}
try {
errorMessage = currentState.get();
} catch (IOException ignored) {
}
if (isEmpty(errorMessage) && !isEmpty(str)) {
errorMessage = "foo";
}
if (isEmpty(errorMessage)) {
try {
errorMessage = currentState.get();
} catch (IOException ignored) {
}
}
return errorMessage;
}
}
@@ -8,7 +8,7 @@ class Test {
String a = notNull(getNullable());
@NotNull
String b = <warning descr="The call to notNull always fails, according to its method contracts">notNull</warning>(null);
String b = <warning descr="The call to 'notNull' always fails, according to its method contracts">notNull</warning>(null);
}
@Nullable
@@ -1,5 +1,6 @@
import java.time.LocalDateTime;
import java.util.List;
import java.util.Scanner;
public class LongRangeKnownMethods {
void testIndexOf(String s) {
@@ -81,7 +82,7 @@ public class LongRangeKnownMethods {
}
void testEqualsIgnoreCase(String s) {
if(s.equalsIgnoreCase("xyz") && s.isEmpty()) {
if(<warning descr="Condition 's.equalsIgnoreCase(\"xyz\") && s.isEmpty()' is always 'false'">s.equalsIgnoreCase("xyz") && <warning descr="Condition 's.isEmpty()' is always 'false' when reached">s.isEmpty()</warning></warning>) {
System.out.println("Never");
}
}
@@ -150,4 +151,41 @@ public class LongRangeKnownMethods {
System.out.println("impossible");
}
}
void testStringComparison(String name) {
// Parentheses misplaced -- found in AndroidStudio
if (!(name.equals("layout_width") && <warning descr="Condition '!(name.equals(\"layout_height\"))' is always 'true'">!(name.equals("layout_height"))</warning> &&
<warning descr="Condition '!(name.equals(\"id\"))' is always 'true'">!(name.equals("id"))</warning>)) {
System.out.println("ok");
}
}
void testFlush(MyReader r) {
if(r.getValue().equals("abc")) {
r.readNext();
if(r.getValue().equals("abcd")) {
System.out.println("ok");
}
}
}
void testNoFlush(MyReader r) {
if(r.getValue().equals("abc")) {
if(<warning descr="Condition 'r.getValue().equals(\"abcd\")' is always 'false'">r.getValue().equals("abcd")</warning>) {
System.out.println("ok");
}
}
}
static class MyReader {
private String value = "";
final String getValue() {
return value;
}
void readNext() {
value = new Scanner(System.in).next();
}
}
}
@@ -82,7 +82,7 @@ class OptionalWithoutIsPresent {
}
if (maybe.isPresent()) {
maybe = Optional.empty();
System.out.println(maybe.<warning descr="The call to get always fails, according to its method contracts">get</warning>());
System.out.println(maybe.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>());
}
boolean b = <warning descr="Condition '((maybe.isPresent()))' is always 'false'">((maybe.isPresent()))</warning> && maybe.get() == 1;
boolean c = <warning descr="Condition '(!maybe.isPresent())' is always 'true'">(!maybe.isPresent())</warning> || maybe.get() == 1;
@@ -118,7 +118,7 @@ class OptionalWithoutIsPresent {
boolean absent = !present;
boolean otherAbsent = !!absent;
if(otherAbsent) {
System.out.println(opt.<warning descr="The call to get always fails, according to its method contracts">get</warning>());
System.out.println(opt.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>());
} else {
System.out.println(opt.get());
}
@@ -151,12 +151,12 @@ class OptionalWithoutIsPresent {
o2 = getOptional();
org.junit.Assert.assertTrue(!o2.isPresent());
System.out.println(o2.<warning descr="The call to get always fails, according to its method contracts">get</warning>());
System.out.println(o2.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>());
}
private void checkAsserts2() {
Optional<String> o3 = Optional.empty();
org.testng.Assert.<warning descr="The call to assertTrue always fails, according to its method contracts">assertTrue</warning>(o3.isPresent());
org.testng.Assert.<warning descr="The call to 'assertTrue' always fails, according to its method contracts">assertTrue</warning>(o3.isPresent());
System.out.println(o3.get());
}
@@ -180,7 +180,7 @@ class OptionalWithoutIsPresent {
} else {
test = Optional.empty();
}
System.out.println(test.<warning descr="The call to get always fails, according to its method contracts">get</warning>());
System.out.println(test.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>());
}
@@ -228,7 +228,7 @@ class OptionalWithoutIsPresent {
void order(Optional<String> order, boolean b) {
order.ifPresent(o -> System.out.println(order.get()));
System.out.println(order.orElseGet(() -> order.<warning descr="The call to get always fails, according to its method contracts">get</warning>().trim()));
System.out.println(order.orElseGet(() -> order.<warning descr="The call to 'get' always fails, according to its method contracts">get</warning>().trim()));
}
public static void two(Optional<Object> o1,Optional<Object> o2) {
@@ -359,8 +359,8 @@ class OptionalWithoutIsPresent {
public void testThrowFail(Optional<String> arg) {
if(!arg.isPresent()) {
System.out.println(arg.<warning descr="The call to orElseThrow always fails, according to its method contracts">orElseThrow</warning>(IllegalAccessError::new));
System.out.println(arg.<warning descr="The call to 'orElseThrow' always fails, according to its method contracts">orElseThrow</warning>(IllegalAccessError::new));
}
String res = Optional.<String>empty().<warning descr="The call to orElseThrow always fails, according to its method contracts">orElseThrow</warning>(RuntimeException::new);
String res = Optional.<String>empty().<warning descr="The call to 'orElseThrow' always fails, according to its method contracts">orElseThrow</warning>(RuntimeException::new);
}
}
@@ -190,6 +190,7 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testOtherCallMayChangeFields() { doTest(); }
public void testMethodCallFlushesField() { doTest(); }
public void testDoubleNaN() { doTest(); }
public void testUnknownFloatMayBeNaN() { doTest(); }
public void testBoxedNaN() { doTest(); }
public void testFloatEquality() { doTest(); }
@@ -247,6 +248,9 @@ public class DataFlowInspectionTest extends DataFlowInspectionTestCase {
public void testContractSeveralClauses() { doTest(); }
public void testContractVarargs() { doTest(); }
public void testContractConstructor() { doTest(); }
public void testFlushVariableOnStackToNotNullType() { doTest(); }
public void testCustomContracts() { doTest(); }
public void testBoxingImpliesNotNull() { doTest(); }
public void testLargeIntegersAreNotEqualWhenBoxed() { doTest(); }
@@ -15,7 +15,7 @@
*/
package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.psi.JavaTokenType;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.psi.PsiType;
import com.intellij.util.containers.HashMap;
import org.junit.Test;
@@ -241,14 +241,14 @@ public class LongRangeSetTest {
@Test
public void testFromRelation() {
assertEquals(range(101, Long.MAX_VALUE), range(100, 200).fromRelation(JavaTokenType.GT));
assertEquals(range(100, Long.MAX_VALUE), range(100, 200).fromRelation(JavaTokenType.GE));
assertEquals(range(Long.MIN_VALUE, 199), range(100, 200).fromRelation(JavaTokenType.LT));
assertEquals(range(Long.MIN_VALUE, 200), range(100, 200).fromRelation(JavaTokenType.LE));
assertEquals(range(100, 200), range(100, 200).fromRelation(JavaTokenType.EQEQ));
assertNull(range(100, 200).fromRelation(JavaTokenType.EQ));
assertEquals(fromType(PsiType.LONG), range(100, 200).fromRelation(JavaTokenType.NE));
assertEquals("{-9223372036854775808..99, 101..9223372036854775807}", point(100).fromRelation(JavaTokenType.NE).toString());
assertEquals(range(101, Long.MAX_VALUE), range(100, 200).fromRelation(RelationType.GT));
assertEquals(range(100, Long.MAX_VALUE), range(100, 200).fromRelation(RelationType.GE));
assertEquals(range(Long.MIN_VALUE, 199), range(100, 200).fromRelation(RelationType.LT));
assertEquals(range(Long.MIN_VALUE, 200), range(100, 200).fromRelation(RelationType.LE));
assertEquals(range(100, 200), range(100, 200).fromRelation(RelationType.EQ));
assertNull(range(100, 200).fromRelation(RelationType.IS));
assertEquals(fromType(PsiType.LONG), range(100, 200).fromRelation(RelationType.NE));
assertEquals("{-9223372036854775808..99, 101..9223372036854775807}", point(100).fromRelation(RelationType.NE).toString());
}
@Test