convert PreContract to kotlin data classes, fix NPE

This commit is contained in:
peter
2016-10-26 15:55:37 +02:00
parent cd8327ab76
commit 037ffa0c8e
5 changed files with 143 additions and 233 deletions
@@ -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<PreContract> 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<PreContract> preContracts = methodNode == null || bodyNode == null ? Collections.emptyList() :
new ContractInferenceInterpreter(tree, TreeBackedLighterAST.wrap(methodNode), TreeBackedLighterAST.wrap(bodyNode)).inferContracts();
List<MethodContract> 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);
@@ -149,11 +149,7 @@ class ContractInferenceInterpreter {
}
if (isNegationExpression(expr)) {
List<PreContract> 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) {
@@ -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++) {
@@ -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<MethodContract> 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<MethodContract> 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<MethodContract> 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<MethodContract> 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<ExpressionRange> myExpressionsToCheck;
private final List<PreContract> myContracts;
SideEffectFilter(List<ExpressionRange> expressionsToCheck, List<PreContract> contracts) {
myExpressionsToCheck = expressionsToCheck;
myContracts = contracts;
}
@NotNull
@Override
List<MethodContract> 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<MethodContract> 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<ValueConstraint[]> myStates;
MethodCallContract(ExpressionRange call, List<ValueConstraint[]> states) {
myCall = call;
myStates = states;
}
@NotNull
@Override
List<MethodContract> 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();
}
}
@@ -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<MethodContract>
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<MethodContract> {
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<PsiParameter>,
callArguments: Array<PsiExpression>,
varArgCall: Boolean,
targetContract: MethodContract): MethodContract? {
var answer: Array<MethodContract.ValueConstraint>? = 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<ExpressionRange>, private val contracts: List<PreContract>) : PreContract {
override fun toContracts(method: PsiMethod, body: PsiCodeBlock): List<MethodContract> {
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<Array<MethodContract.ValueConstraint>>) : PreContract {
override fun toContracts(method: PsiMethod, body: PsiCodeBlock): List<MethodContract> {
val target = (call.restoreExpression(body) as PsiMethodCallExpression?)?.resolveMethod()
if (target != null && NullableNotNullManager.isNotNull(target)) {
return ContractInferenceInterpreter.toContracts(states, NOT_NULL_VALUE)
}
return emptyList()
}
}