mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-08-26 15:27:45 +07:00
Custom relation-based contracts; hardcoded contracts for string methods (charAt, substring)
This commit is contained in:
+3
-3
@@ -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
|
||||
|
||||
+5
-4
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -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;
|
||||
});
|
||||
|
||||
+20
-20
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -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;
|
||||
}
|
||||
|
||||
+12
-9
@@ -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
-17
@@ -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);
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-4
@@ -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);
|
||||
|
||||
+78
-86
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+152
-53
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
+39
-102
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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) })
|
||||
|
||||
+97
-142
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+134
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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)
|
||||
|
||||
+39
-26
@@ -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)
|
||||
*
|
||||
|
||||
+5
@@ -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;
|
||||
|
||||
+141
-121
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+91
-4
@@ -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();
|
||||
}
|
||||
|
||||
+2
-2
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-8
@@ -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(); }
|
||||
|
||||
+9
-9
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user