diff --git a/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java b/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java index f3ba136b966e..cf7b6f9f7f76 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInsight/InferredAnnotationsManagerImpl.java @@ -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 contracts, boolean pure) { - return createContractAnnotation(myProject, pure, StringUtil.join(contracts, "; ")); + private PsiAnnotation createContractAnnotation(List contracts, boolean pure) { + return createContractAnnotation(myProject, pure, StreamEx.of(contracts).select(StandardMethodContract.class).joining("; ")); } @Nullable diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java index e6a3436fa072..c3ebffab8cdd 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractChecker.java @@ -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 myViolations = ContainerUtil.newHashSet(); private final Set myNonViolations = ContainerUtil.newHashSet(); private final Set myFailures = ContainerUtil.newHashSet(); - private ContractChecker(PsiMethod method, MethodContract contract) { + private ContractChecker(PsiMethod method, StandardMethodContract contract) { super(false, true); myMethod = method; myContract = contract; } - static Map checkContractClause(PsiMethod method, MethodContract contract, boolean ignoreAssertions) { + static Map 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)); } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java index 24f8357459fb..de08c1bc2d3d 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInference.java @@ -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 inferContracts(@NotNull PsiMethodImpl method) { + public static List 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 preContracts = data == null ? Collections.emptyList() : data.getContracts(); - List result = RecursionManager.doPreventingRecursion(method, true, () -> postProcessContracts(method, data, preContracts)); + List 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 postProcessContracts(@NotNull PsiMethodImpl method, MethodData data, List rawContracts) { - List contracts = ContainerUtil.concat(rawContracts, c -> c.toContracts(method, data.methodBody(method))); + private static List postProcessContracts(@NotNull PsiMethodImpl method, MethodData data, List rawContracts) { + List 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 compatible = ContainerUtil.filter(contracts, contract -> isContractCompatibleWithMethod(method, returnType, contract)); + List 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 boxReturnValues(List contracts) { + private static List boxReturnValues(List 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; }); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceInterpreter.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceInterpreter.java index 111fb4442ea1..ada322f9f016 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceInterpreter.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceInterpreter.java @@ -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 result = ContainerUtil.newArrayList(); + List 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 visitPolyadic(List 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 operands = getExpressionChildren(myTree, expr); @@ -206,22 +206,22 @@ class ContractInferenceInterpreter { } @NotNull - private static List asPreContracts(List contracts) { + private static List asPreContracts(List 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 visitEqualityComparison(List states, - LighterASTNode op1, - LighterASTNode op2, - boolean equality) { + private List visitEqualityComparison(List 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 result = ContainerUtil.newArrayList(); + List 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 toContracts(List states, ValueConstraint constraint) { - return ContainerUtil.map(states, state -> new MethodContract(state, constraint)); + static List toContracts(List states, ValueConstraint constraint) { + return ContainerUtil.map(states, state -> new StandardMethodContract(state, constraint)); } - private List visitLogicalOperation(List operands, boolean conjunction, List states) { + private List visitLogicalOperation(List operands, boolean conjunction, List states) { ValueConstraint breakValue = conjunction ? FALSE_VALUE : TRUE_VALUE; - List finalStates = ContainerUtil.newArrayList(); + List finalStates = ContainerUtil.newArrayList(); for (LighterASTNode operand : operands) { List 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 knownContracts(List values) { + private static List knownContracts(List values) { return ContainerUtil.mapNotNull(values, pc -> pc instanceof KnownContract ? ((KnownContract)pc).getContract() : null); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInspection.java index ae986149dc20..f59d88660ee4 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInspection.java @@ -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 errors = ContractChecker.checkContractClause(method, contract, false); for (Map.Entry 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 contracts; + List 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; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java index 9407a57d5aec..c659ad95d059 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ControlFlowAnalyzer.java @@ -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 contracts = method instanceof PsiMethod ? getMethodCallContracts((PsiMethod)method, expression) : Collections.emptyList(); + List 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 getMethodCallContracts(@NotNull final PsiMethod method, @NotNull PsiMethodCallExpression call) { + private static List getMethodCallContracts(@NotNull final PsiMethod method, + @NotNull PsiMethodCallExpression call) { List contracts = HardcodedContracts.getHardcodedContracts(method, call); return !contracts.isEmpty() ? contracts : getMethodContracts(method); } - public static List getMethodContracts(@NotNull final PsiMethod method) { + public static List 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 applicable = ContainerUtil.filter(MethodContract.parseContract(text), - contract -> contract.arguments.length == paramCount); + List 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.emptyList(), method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); + return CachedValueProvider.Result + .create(Collections.emptyList(), method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); }); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java index c5a3295229a7..159b7282c83a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CustomMethodHandlers.java @@ -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 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 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 applyCondition(DfaMemoryState memState, - DfaRelationValue trueRelation, + DfaValue trueCondition, DfaValue trueResult, - DfaRelationValue falseRelation, + DfaValue falseCondition, DfaValue falseResult) { DfaMemoryState falseState = memState.createCopy(); List 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); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java index 41864a95ad7e..112d5f47a3c3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DataFlowInspectionBase.java @@ -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 diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java index 43ef79454fdc..8c92f05636cd 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryState.java @@ -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); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java index 81990047f9df..bf9a117fdfc3 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaMemoryStateImpl.java @@ -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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java index e37d308cecef..007e0b5638b0 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/DfaUtil.java @@ -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; + } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java index 4d53a925a89d..2ee27d0239d8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java @@ -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 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 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 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 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 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) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java index b76aa964ec22..076814d3204a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/InferenceFromSourceUtil.java @@ -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; } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodContract.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodContract.java index b61aa1015216..aa76b93e8464 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodContract.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodContract.java @@ -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 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 parseContract(String text) throws ParseException { - List 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); - } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodDataExternalizer.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodDataExternalizer.kt index 039bf87a9236..eca41a668c8a 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodDataExternalizer.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/MethodDataExternalizer.kt @@ -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> } 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) }) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java index ed0a7cfefab2..9fafc37a9593 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardInstructionVisitor.java @@ -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 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 finalStates) { - DfaConstValue.Factory constFactory = factory.getConstFactory(); + List 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 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 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 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 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 states = new ArrayList<>(); + ArrayList 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; } - } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardMethodContract.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardMethodContract.java new file mode 100644 index 000000000000..739db41cca17 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StandardMethodContract.java @@ -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 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 parseContract(String text) throws ParseException { + List 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); + } + } +} diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java index 6a08a151497c..6df7b7a89b33 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/StateMerger.java @@ -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)); } } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java index 295b53832ce8..e4b6b420622e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/instructions/MethodCallInstruction.java @@ -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 contracts) { + public MethodCallInstruction(@NotNull PsiCall call, @Nullable DfaValue precalculatedReturnValue, List contracts) { myContext = call; - myContracts = contracts; + myContracts = Collections.unmodifiableList(contracts); myMethodType = MethodType.REGULAR_METHOD_CALL; myCall = call; final PsiExpressionList argList = call.getArgumentList(); diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/preContracts.kt b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/preContracts.kt index 2c3c7901f0c5..3504ff1b9cf8 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/preContracts.kt +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/preContracts.kt @@ -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 + fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List 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 { + override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List { 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, callArguments: Array, varArgCall: Boolean, - targetContract: MethodContract): MethodContract? { + targetContract: StandardMethodContract): StandardMethodContract? { var answer: Array? = 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, internal val contracts: List) : PreContract { - override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List { + override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List { 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>) : PreContract { override fun hashCode() = call.hashCode() * 31 + states.flatten().map { it.ordinal }.hashCode() - override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List { + override fun toContracts(method: PsiMethod, body: () -> PsiCodeBlock): List { val target = (call.restoreExpression(body()) as PsiMethodCallExpression?)?.resolveMethod() if (target != null && NullableNotNullManager.isNotNull(target)) { return ContractInferenceInterpreter.toContracts(states.map { it.toTypedArray() }, NOT_NULL_VALUE) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java index 306c91c01bd2..56aea001e759 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSet.java @@ -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) * diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRangeValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRangeValue.java index 47825a92cf30..f2a56c182e14 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRangeValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRangeValue.java @@ -42,6 +42,11 @@ public class DfaRangeValue extends DfaValue { return myValue; } + @Override + public String toString() { + return myValue.toString(); + } + public static class Factory { private Map myValues = new HashMap<>(); private DfaValueFactory myFactory; diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelationValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelationValue.java index c77b320603ec..029208ed5209 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelationValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaRelationValue.java @@ -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> 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 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, 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; } } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java index fd29e0c5b37a..ad65d18be27e 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaValueFactory.java @@ -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(); } diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java index 05c4bdd1babc..05613f764bac 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/value/DfaVariableValue.java @@ -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; } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/ContractConstructor.java b/java/java-tests/testData/inspection/dataFlow/fixture/ContractConstructor.java index c29ed07070d1..fc1d5fa6cae8 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/ContractConstructor.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/ContractConstructor.java @@ -7,8 +7,8 @@ class CandidateInfo { } static void test() { - new CandidateInfo(null, true, false); - new CandidateInfo(null, true, true); + new CandidateInfo(null, true, false); + new CandidateInfo(null, true, true); new CandidateInfo(null, false, true); new CandidateInfo(new Object(), true, true); diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/CustomContracts.java b/java/java-tests/testData/inspection/dataFlow/fixture/CustomContracts.java new file mode 100644 index 000000000000..942a7c3173bd --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/CustomContracts.java @@ -0,0 +1,30 @@ +public class CustomContracts { + public void testSubstring(String s) { + if (s.substring(-1).length() == 0) { + System.out.println("Oops"); + } + } + + public void testSubstring2(String s, int index) { + if (s.substring(0, index).length() == 0 || index < 0) { + System.out.println("Oops"); + } + } + + public void testSubstring3(String s, int index) { + if (s.substring(3, index).equals("foo") || index == 1) { + System.out.println("Oops"); + } + } + + public void testCharAt(String s) { + int index = 0; + while (Character.isDigit(s.charAt(index))) { + index++; + } + + if (index == 0 || index == s.length()) { + System.out.println("Wrong"); + } + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/DoubleNaN.java b/java/java-tests/testData/inspection/dataFlow/fixture/DoubleNaN.java new file mode 100644 index 000000000000..714035000412 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/DoubleNaN.java @@ -0,0 +1,9 @@ +public class DoubleNaN { + void test() { + double x = Double.NaN; + double y = Double.NaN; + if(x >= y) { + System.out.println("oops"); + } + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/FlushVariableOnStackToNotNullType.java b/java/java-tests/testData/inspection/dataFlow/fixture/FlushVariableOnStackToNotNullType.java new file mode 100644 index 000000000000..c757c8d09861 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/FlushVariableOnStackToNotNullType.java @@ -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; + } +} diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/GenericParameterNullity.java b/java/java-tests/testData/inspection/dataFlow/fixture/GenericParameterNullity.java index e25c89ab650f..500b5e4bfc55 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/GenericParameterNullity.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/GenericParameterNullity.java @@ -8,7 +8,7 @@ class Test { String a = notNull(getNullable()); @NotNull - String b = notNull(null); + String b = notNull(null); } @Nullable diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java index de8b54e5f43a..db2d9d81952f 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/LongRangeKnownMethods.java @@ -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(s.equalsIgnoreCase("xyz") && s.isEmpty()) { 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") && !(name.equals("layout_height")) && + !(name.equals("id")))) { + 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(r.getValue().equals("abcd")) { + System.out.println("ok"); + } + } + } + + static class MyReader { + private String value = ""; + + final String getValue() { + return value; + } + + void readNext() { + value = new Scanner(System.in).next(); + } + } } diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java index 9642644ebcf3..4b4c74dc02df 100644 --- a/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java +++ b/java/java-tests/testData/inspection/dataFlow/fixture/OptionalGetWithoutIsPresent.java @@ -82,7 +82,7 @@ class OptionalWithoutIsPresent { } if (maybe.isPresent()) { maybe = Optional.empty(); - System.out.println(maybe.get()); + System.out.println(maybe.get()); } boolean b = ((maybe.isPresent())) && maybe.get() == 1; boolean c = (!maybe.isPresent()) || maybe.get() == 1; @@ -118,7 +118,7 @@ class OptionalWithoutIsPresent { boolean absent = !present; boolean otherAbsent = !!absent; if(otherAbsent) { - System.out.println(opt.get()); + System.out.println(opt.get()); } else { System.out.println(opt.get()); } @@ -151,12 +151,12 @@ class OptionalWithoutIsPresent { o2 = getOptional(); org.junit.Assert.assertTrue(!o2.isPresent()); - System.out.println(o2.get()); + System.out.println(o2.get()); } private void checkAsserts2() { Optional o3 = Optional.empty(); - org.testng.Assert.assertTrue(o3.isPresent()); + org.testng.Assert.assertTrue(o3.isPresent()); System.out.println(o3.get()); } @@ -180,7 +180,7 @@ class OptionalWithoutIsPresent { } else { test = Optional.empty(); } - System.out.println(test.get()); + System.out.println(test.get()); } @@ -228,7 +228,7 @@ class OptionalWithoutIsPresent { void order(Optional order, boolean b) { order.ifPresent(o -> System.out.println(order.get())); - System.out.println(order.orElseGet(() -> order.get().trim())); + System.out.println(order.orElseGet(() -> order.get().trim())); } public static void two(Optional o1,Optional o2) { @@ -359,8 +359,8 @@ class OptionalWithoutIsPresent { public void testThrowFail(Optional arg) { if(!arg.isPresent()) { - System.out.println(arg.orElseThrow(IllegalAccessError::new)); + System.out.println(arg.orElseThrow(IllegalAccessError::new)); } - String res = Optional.empty().orElseThrow(RuntimeException::new); + String res = Optional.empty().orElseThrow(RuntimeException::new); } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java index 666d3e139975..8ee0a36df387 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/DataFlowInspectionTest.java @@ -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(); } diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java index 1a31fcd04165..1f09379891eb 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/dataFlow/rangeSet/LongRangeSetTest.java @@ -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