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 6856d13624a1..4509e0aff2d2 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 @@ -53,9 +53,10 @@ public class ContractInference { TreeBackedLighterAST tree = new TreeBackedLighterAST(method.getContainingFile().getNode()); PsiCodeBlock body = method.getBody(); assert body != null; - ASTNode node = method.getNode(); - List preContracts = node == null ? Collections.emptyList() : - new ContractInferenceInterpreter(tree, TreeBackedLighterAST.wrap(node), TreeBackedLighterAST.wrap(body.getNode())).inferContracts(); + ASTNode methodNode = method.getNode(); + ASTNode bodyNode = body.getNode(); + List preContracts = methodNode == null || bodyNode == null ? Collections.emptyList() : + new ContractInferenceInterpreter(tree, TreeBackedLighterAST.wrap(methodNode), TreeBackedLighterAST.wrap(bodyNode)).inferContracts(); List result = RecursionManager.doPreventingRecursion(method, true, () -> postProcessContracts(method, body, preContracts)); if (result == null) result = Collections.emptyList(); return CachedValueProvider.Result.create(result, method, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); 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 c4a139311de8..2f78283ac76b 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 @@ -149,11 +149,7 @@ class ContractInferenceInterpreter { } if (isNegationExpression(expr)) { - List result = ContainerUtil.newArrayList(); - for (PreContract contract : visitExpression(states, findExpressionChild(myTree, expr))) { - ContainerUtil.addIfNotNull(result, NegatingContract.negate(contract)); - } - return result; + return ContainerUtil.mapNotNull(visitExpression(states, findExpressionChild(myTree, expr)), PreContract::negate); } if (type == INSTANCE_OF_EXPRESSION) { 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 0f3ba3ae4aec..3d92a10278ae 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 @@ -18,7 +18,6 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInspection.dataFlow.value.DfaConstValue; import com.intellij.codeInspection.dataFlow.value.DfaValueFactory; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,6 +36,7 @@ public class MethodContract { this.returnValue = returnValue; } + @NotNull static ValueConstraint[] createConstraintArray(int paramCount) { ValueConstraint[] args = new ValueConstraint[paramCount]; for (int i = 0; i < args.length; i++) { diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PreContract.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PreContract.java deleted file mode 100644 index cdbc20fd9fe8..000000000000 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/PreContract.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2000-2016 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.codeInsight.NullableNotNullManager; -import com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint; -import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction; -import com.intellij.psi.*; -import com.intellij.util.containers.ContainerUtil; -import com.siyeh.ig.psiutils.SideEffectChecker; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collections; -import java.util.List; - -import static com.intellij.codeInspection.dataFlow.ContractInferenceInterpreter.*; -import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint.*; - -/** - * @author peter - */ -public abstract class PreContract { - - @NotNull - abstract List toContracts(@NotNull PsiMethod method, @NotNull PsiCodeBlock body); - -} - -class KnownContract extends PreContract { - private final MethodContract myKnownContract; - - KnownContract(@NotNull MethodContract knownContract) { - myKnownContract = knownContract; - } - - @NotNull - MethodContract getContract() { - return myKnownContract; - } - - @NotNull - @Override - List toContracts(@NotNull PsiMethod method, @NotNull PsiCodeBlock body) { - return Collections.singletonList(myKnownContract); - } -} - -class DelegationContract extends PreContract { - - private final ExpressionRange myExpression; - private final boolean myNegated; - - DelegationContract(ExpressionRange expression, boolean negated) { - myExpression = expression; - myNegated = negated; - } - - @NotNull - @Override - List toContracts(@NotNull PsiMethod method, @NotNull PsiCodeBlock body) { - PsiMethodCallExpression call = (PsiMethodCallExpression)myExpression.restoreExpression(body); - if (call == null) return Collections.emptyList(); - - JavaResolveResult result = call.resolveMethodGenerics(); - - final PsiMethod targetMethod = (PsiMethod)result.getElement(); - if (targetMethod == null) return Collections.emptyList(); - - final PsiParameter[] parameters = targetMethod.getParameterList().getParameters(); - final PsiExpression[] arguments = call.getArgumentList().getExpressions(); - final boolean varArgCall = MethodCallInstruction.isVarArgCall(targetMethod, result.getSubstitutor(), arguments, parameters); - - List fromDelegate = ContainerUtil.mapNotNull(ControlFlowAnalyzer.getMethodContracts(targetMethod), - dc -> convertDelegatedMethodContract(method, parameters, arguments, varArgCall, dc)); - if (NullableNotNullManager.isNotNull(targetMethod)) { - return ContainerUtil.concat(ContainerUtil.map(fromDelegate, DelegationContract::returnNotNull), - Collections.singletonList(new MethodContract(emptyConstraints(method), NOT_NULL_VALUE))); - } - return fromDelegate; - } - - @Nullable - private MethodContract convertDelegatedMethodContract(@NotNull PsiMethod callerMethod, - PsiParameter[] targetParameters, - PsiExpression[] callArguments, - boolean varArgCall, - MethodContract targetContract) { - ValueConstraint[] answer = emptyConstraints(callerMethod); - for (int i = 0; i < targetContract.arguments.length; i++) { - if (i >= callArguments.length) return null; - ValueConstraint argConstraint = targetContract.arguments[i]; - if (argConstraint != ANY_VALUE) { - if (varArgCall && i >= targetParameters.length - 1) { - if (argConstraint == NULL_VALUE) { - return null; - } - break; - } - - PsiExpression argument = callArguments[i]; - int paramIndex = resolveParameter(callerMethod, argument); - if (paramIndex >= 0) { - answer = withConstraint(answer, paramIndex, argConstraint); - if (answer == null) { - return null; - } - } - else if (argConstraint != getLiteralConstraint(argument)) { - return null; - } - } - } - ValueConstraint returnValue = myNegated ? negateConstraint(targetContract.returnValue) : targetContract.returnValue; - return answer == null ? null : new MethodContract(answer, returnValue); - } - - private static ValueConstraint[] emptyConstraints(@NotNull PsiMethod method) { - return MethodContract.createConstraintArray(method.getParameterList().getParametersCount()); - } - - @NotNull - private static MethodContract returnNotNull(MethodContract mc) { - return mc.returnValue == THROW_EXCEPTION ? mc : new MethodContract(mc.arguments, NOT_NULL_VALUE); - } - - private static ValueConstraint getLiteralConstraint(PsiExpression argument) { - return argument instanceof PsiLiteralExpression ? ContractInferenceInterpreter.getLiteralConstraint(argument.getFirstChild().getNode().getElementType()) : null; - } - - private static int resolveParameter(@NotNull PsiMethod method, PsiExpression expr) { - PsiElement target = expr instanceof PsiReferenceExpression && !((PsiReferenceExpression)expr).isQualified() - ? ((PsiReferenceExpression)expr).resolve() : null; - return target instanceof PsiParameter && target.getParent() == method.getParameterList() - ? method.getParameterList().getParameterIndex((PsiParameter)target) : -1; - } -} - -class SideEffectFilter extends PreContract { - private final List myExpressionsToCheck; - private final List myContracts; - - SideEffectFilter(List expressionsToCheck, List contracts) { - myExpressionsToCheck = expressionsToCheck; - myContracts = contracts; - } - - @NotNull - @Override - List toContracts(@NotNull PsiMethod method, @NotNull PsiCodeBlock body) { - if (ContainerUtil.exists(myExpressionsToCheck, d -> mayHaveSideEffects(body, d))) { - return Collections.emptyList(); - } - return ContainerUtil.concat(myContracts, c -> c.toContracts(method, body)); - } - - private static boolean mayHaveSideEffects(PsiCodeBlock body, ExpressionRange range) { - PsiExpression exp = range.restoreExpression(body); - return exp != null && SideEffectChecker.mayHaveSideEffects(exp); - } -} - -class NegatingContract extends PreContract { - private final PreContract myNegated; - - private NegatingContract(PreContract negated) { - myNegated = negated; - } - - @NotNull - @Override - List toContracts(@NotNull PsiMethod method, @NotNull PsiCodeBlock body) { - return ContainerUtil.mapNotNull(myNegated.toContracts(method, body), NegatingContract::negateContract); - } - - @Nullable - static PreContract negate(@NotNull PreContract contract) { - if (contract instanceof KnownContract) { - MethodContract negated = negateContract(((KnownContract)contract).getContract()); - return negated == null ? null : new KnownContract(negated); - } - return new NegatingContract(contract); - } - - @Nullable - private static MethodContract negateContract(MethodContract c) { - ValueConstraint ret = c.returnValue; - return ret == TRUE_VALUE || ret == FALSE_VALUE ? new MethodContract(c.arguments, negateConstraint(ret)) : null; - } -} - -class MethodCallContract extends PreContract { - private final ExpressionRange myCall; - private final List myStates; - - MethodCallContract(ExpressionRange call, List states) { - myCall = call; - myStates = states; - } - - @NotNull - @Override - List toContracts(@NotNull PsiMethod method, @NotNull PsiCodeBlock body) { - PsiMethodCallExpression call = (PsiMethodCallExpression)myCall.restoreExpression(body); - PsiMethod target = call == null ? null : call.resolveMethod(); - if (target != null && NullableNotNullManager.isNotNull(target)) { - return ContractInferenceInterpreter.toContracts(myStates, NOT_NULL_VALUE); - } - return Collections.emptyList(); - } -} 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 new file mode 100644 index 000000000000..ed6b52164e61 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/preContracts.kt @@ -0,0 +1,137 @@ +/* + * Copyright 2000-2016 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.codeInsight.NullableNotNullManager +import com.intellij.codeInspection.dataFlow.ContractInferenceInterpreter.negateConstraint +import com.intellij.codeInspection.dataFlow.ContractInferenceInterpreter.withConstraint +import com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint.* +import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction +import com.intellij.psi.* +import com.siyeh.ig.psiutils.SideEffectChecker + +/** + * @author peter + */ +interface PreContract { + fun toContracts(method: PsiMethod, body: PsiCodeBlock): List + fun negate(): PreContract? = NegatingContract(this) +} + +internal data class KnownContract(val contract: MethodContract) : PreContract { + override fun toContracts(method: PsiMethod, body: PsiCodeBlock) = listOf(contract) + override fun negate() = negateContract(contract)?.let(::KnownContract) +} + +internal data class DelegationContract(private val expression: ExpressionRange, private val negated: Boolean) : PreContract { + + override fun toContracts(method: PsiMethod, body: PsiCodeBlock): List { + val call = expression.restoreExpression(body) as PsiMethodCallExpression? ?: return emptyList() + + val result = call.resolveMethodGenerics() + val targetMethod = result.element as PsiMethod? ?: return emptyList() + + val parameters = targetMethod.parameterList.parameters + val arguments = call.argumentList.expressions + val varArgCall = MethodCallInstruction.isVarArgCall(targetMethod, result.substitutor, arguments, parameters) + + val fromDelegate = ControlFlowAnalyzer.getMethodContracts(targetMethod).mapNotNull { dc -> + convertDelegatedMethodContract(method, parameters, arguments, varArgCall, dc) + } + if (NullableNotNullManager.isNotNull(targetMethod)) { + return fromDelegate.map { returnNotNull(it) } + listOf(MethodContract(emptyConstraints(method), NOT_NULL_VALUE)) + } + return fromDelegate + } + + private fun convertDelegatedMethodContract(callerMethod: PsiMethod, + targetParameters: Array, + callArguments: Array, + varArgCall: Boolean, + targetContract: MethodContract): MethodContract? { + var answer: Array? = emptyConstraints(callerMethod) + for (i in targetContract.arguments.indices) { + if (i >= callArguments.size) return null + val argConstraint = targetContract.arguments[i] + if (argConstraint != ANY_VALUE) { + if (varArgCall && i >= targetParameters.size - 1) { + if (argConstraint == NULL_VALUE) { + return null + } + break + } + + val argument = callArguments[i] + val paramIndex = resolveParameter(callerMethod, argument) + if (paramIndex >= 0) { + answer = withConstraint(answer, paramIndex, argConstraint) ?: return null + } + else if (argConstraint != getLiteralConstraint(argument)) { + return null + } + } + } + val returnValue = if (negated) negateConstraint(targetContract.returnValue) else targetContract.returnValue + return answer?.let { MethodContract(it, returnValue) } + } + + private fun emptyConstraints(method: PsiMethod) = MethodContract.createConstraintArray(method.parameterList.parametersCount) + + private fun returnNotNull(mc: MethodContract) = if (mc.returnValue == THROW_EXCEPTION) mc else MethodContract(mc.arguments, NOT_NULL_VALUE) + + private fun getLiteralConstraint(argument: PsiExpression) = when (argument) { + is PsiLiteralExpression -> ContractInferenceInterpreter.getLiteralConstraint(argument.getFirstChild().node.elementType) + else -> null + } + + private fun resolveParameter(method: PsiMethod, expr: PsiExpression): Int { + val target = if (expr is PsiReferenceExpression && !expr.isQualified) expr.resolve() else null + return if (target is PsiParameter && target.parent === method.parameterList) method.parameterList.getParameterIndex(target) else -1 + } +} + +internal data class SideEffectFilter(private val expressionsToCheck: List, private val contracts: List) : PreContract { + + override fun toContracts(method: PsiMethod, body: PsiCodeBlock): List { + if (expressionsToCheck.any { d -> mayHaveSideEffects(body, d) }) { + return emptyList() + } + return contracts.flatMap { c -> c.toContracts(method, body) } + } + + private fun mayHaveSideEffects(body: PsiCodeBlock, range: ExpressionRange) = + range.restoreExpression(body)?.let { SideEffectChecker.mayHaveSideEffects(it) } ?: false +} + +internal data class NegatingContract(private val negated: PreContract) : PreContract { + override fun toContracts(method: PsiMethod, body: PsiCodeBlock) = negated.toContracts(method, body).mapNotNull(::negateContract) +} + +private fun negateContract(c: MethodContract): MethodContract? { + val ret = c.returnValue + return if (ret == TRUE_VALUE || ret == FALSE_VALUE) MethodContract(c.arguments, negateConstraint(ret)) else null +} + +internal data class MethodCallContract(private val call: ExpressionRange, private val states: List>) : PreContract { + + 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, NOT_NULL_VALUE) + } + return emptyList() + } +}