From cd8327ab765658393432f4235f2a3eae923cc891 Mon Sep 17 00:00:00 2001 From: peter Date: Wed, 26 Oct 2016 15:29:05 +0200 Subject: [PATCH] move ContractInferenceInterpreter to a separate file otherwise it can't be referenced from Kotlin (KT-4455) --- .../dataFlow/ContractInference.java | 380 +---------------- .../ContractInferenceInterpreter.java | 400 ++++++++++++++++++ 2 files changed, 404 insertions(+), 376 deletions(-) create mode 100644 java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceInterpreter.java 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 839b1bb5f836..6856d13624a1 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 @@ -16,35 +16,25 @@ package com.intellij.codeInspection.dataFlow; import com.intellij.codeInsight.NullableNotNullManager; -import com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint; import com.intellij.lang.ASTNode; -import com.intellij.lang.LighterAST; -import com.intellij.lang.LighterASTNode; import com.intellij.lang.TreeBackedLighterAST; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.RecursionManager; -import com.intellij.psi.*; -import com.intellij.psi.impl.source.JavaLightTreeUtil; -import com.intellij.psi.impl.source.tree.ElementType; -import com.intellij.psi.tree.IElementType; +import com.intellij.psi.PsiCodeBlock; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.PsiPrimitiveType; +import com.intellij.psi.PsiType; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.PsiUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint.*; -import static com.intellij.psi.impl.source.JavaLightTreeUtil.findExpressionChild; -import static com.intellij.psi.impl.source.JavaLightTreeUtil.getExpressionChildren; -import static com.intellij.psi.impl.source.tree.JavaElementType.*; -import static com.intellij.psi.impl.source.tree.LightTreeUtil.firstChildOfType; -import static com.intellij.psi.impl.source.tree.LightTreeUtil.getChildrenOfType; /** * @author peter @@ -111,366 +101,4 @@ public class ContractInference { }); } -} - -class ContractInferenceInterpreter { - private final LighterAST myTree; - private final LighterASTNode myMethod; - private final LighterASTNode myBody; - - public ContractInferenceInterpreter(LighterAST tree, LighterASTNode method, LighterASTNode body) { - myTree = tree; - myMethod = method; - myBody = body; - } - - @NotNull - private List getParameters() { - LighterASTNode paramList = firstChildOfType(myTree, myMethod, PARAMETER_LIST); - return paramList != null ? getChildrenOfType(myTree, paramList, PARAMETER) : Collections.emptyList(); - } - - List inferContracts() { - LighterASTNode[] statements = getStatements(myBody); - if (statements.length == 0) return Collections.emptyList(); - - if (statements.length == 1) { - if (statements[0].getTokenType() == RETURN_STATEMENT) { - List result = handleDelegation(findExpressionChild(myTree, statements[0]), false); - if (result != null) { - return result; - } - } - else if (statements[0].getTokenType() == EXPRESSION_STATEMENT) { - LighterASTNode expr = findExpressionChild(myTree, statements[0]); - List result = expr != null && expr.getTokenType() == METHOD_CALL_EXPRESSION ? handleDelegation(expr, false) : null; - if (result != null) return result; - } - } - - return visitStatements(Collections.singletonList(MethodContract.createConstraintArray(getParameters().size())), statements); - } - - @Nullable - private LighterASTNode getCodeBlock(@Nullable LighterASTNode parent) { - return firstChildOfType(myTree, parent, CODE_BLOCK); - } - - @NotNull - private LighterASTNode[] getStatements(@Nullable LighterASTNode codeBlock) { - return codeBlock == null - ? LighterASTNode.EMPTY_ARRAY - : getChildrenOfType(myTree, codeBlock, ElementType.JAVA_STATEMENT_BIT_SET).toArray(LighterASTNode.EMPTY_ARRAY); - } - - @Nullable - private List handleDelegation(@Nullable LighterASTNode expression, boolean negated) { - if (expression == null) return null; - if (expression.getTokenType() == PARENTH_EXPRESSION) { - return handleDelegation(findExpressionChild(myTree, expression), negated); - } - - if (isNegationExpression(expression)) { - return handleDelegation(findExpressionChild(myTree, expression), !negated); - } - - if (expression.getTokenType() == METHOD_CALL_EXPRESSION) { - return Collections.singletonList(new DelegationContract(ExpressionRange.create(expression, myBody.getStartOffset()), negated)); - } - - return null; - } - - private boolean isNegationExpression(@Nullable LighterASTNode expression) { - return expression != null && expression.getTokenType() == PREFIX_EXPRESSION && firstChildOfType(myTree, expression, JavaTokenType.EXCL) != null; - } - - @NotNull - private List visitExpression(final List states, @Nullable LighterASTNode expr) { - if (expr == null) return Collections.emptyList(); - if (states.isEmpty()) return Collections.emptyList(); - if (states.size() > 300) return Collections.emptyList(); // too complex - - IElementType type = expr.getTokenType(); - if (type == POLYADIC_EXPRESSION || type == BINARY_EXPRESSION) { - List operands = getExpressionChildren(myTree, expr); - if (operands.size() == 2) { - boolean equality = firstChildOfType(myTree, expr, JavaTokenType.EQEQ) != null; - if (equality || firstChildOfType(myTree, expr, JavaTokenType.NE) != null) { - return asPreContracts(visitEqualityComparison(states, operands.get(0), operands.get(1), equality)); - } - } - boolean logicalAnd = firstChildOfType(myTree, expr, JavaTokenType.ANDAND) != null; - if (logicalAnd || firstChildOfType(myTree, expr, JavaTokenType.OROR) != null) { - return asPreContracts(visitLogicalOperation(operands, logicalAnd, states)); - } - } - - if (type == CONDITIONAL_EXPRESSION) { - List children = getExpressionChildren(myTree, expr); - if (children.size() != 3) return Collections.emptyList(); - - List conditionResults = visitExpression(states, children.get(0)); - return ContainerUtil.concat( - visitExpression(antecedentsReturning(conditionResults, TRUE_VALUE), children.get(1)), - visitExpression(antecedentsReturning(conditionResults, FALSE_VALUE), children.get(2))); - } - - - if (type == PARENTH_EXPRESSION) { - return visitExpression(states, findExpressionChild(myTree, expr)); - } - if (type == TYPE_CAST_EXPRESSION) { - return visitExpression(states, findExpressionChild(myTree, expr)); - } - - if (isNegationExpression(expr)) { - List result = ContainerUtil.newArrayList(); - for (PreContract contract : visitExpression(states, findExpressionChild(myTree, expr))) { - ContainerUtil.addIfNotNull(result, NegatingContract.negate(contract)); - } - return result; - } - - if (type == INSTANCE_OF_EXPRESSION) { - final int parameter = resolveParameter(findExpressionChild(myTree, expr)); - if (parameter >= 0) { - return asPreContracts(ContainerUtil.mapNotNull(states, state -> contractWithConstraint(state, parameter, NULL_VALUE, FALSE_VALUE))); - } - } - - if (type == NEW_EXPRESSION) { - return asPreContracts(toContracts(states, NOT_NULL_VALUE)); - } - if (type == METHOD_CALL_EXPRESSION) { - return Collections.singletonList(new MethodCallContract(ExpressionRange.create(expr, myBody.getStartOffset()), states)); - } - - final ValueConstraint constraint = getLiteralConstraint(expr); - if (constraint != null) { - return asPreContracts(toContracts(states, constraint)); - } - - int paramIndex = resolveParameter(expr); - if (paramIndex >= 0) { - 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])); - } else if (JavaTokenType.BOOLEAN_KEYWORD == getPrimitiveParameterType(paramIndex)) { - // if (boolValue) ... - ContainerUtil.addIfNotNull(result, contractWithConstraint(state, paramIndex, TRUE_VALUE, TRUE_VALUE)); - ContainerUtil.addIfNotNull(result, contractWithConstraint(state, paramIndex, FALSE_VALUE, FALSE_VALUE)); - } - } - return asPreContracts(result); - } - - return Collections.emptyList(); - } - - @NotNull - 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) { - ValueConstraint[] newState = withConstraint(state, parameter, paramConstraint); - return newState == null ? null : new MethodContract(newState, returnValue); - } - - private List visitEqualityComparison(List states, - LighterASTNode op1, - LighterASTNode op2, - boolean equality) { - int parameter = resolveParameter(op1); - ValueConstraint constraint = getLiteralConstraint(op2); - if (parameter < 0 || constraint == null) { - parameter = resolveParameter(op2); - constraint = getLiteralConstraint(op1); - } - if (parameter >= 0 && constraint != null) { - List result = ContainerUtil.newArrayList(); - for (ValueConstraint[] state : states) { - if (constraint == NOT_NULL_VALUE) { - if (getPrimitiveParameterType(parameter) == null) { - ContainerUtil.addIfNotNull(result, contractWithConstraint(state, parameter, NULL_VALUE, equality ? FALSE_VALUE : TRUE_VALUE)); - } - } else { - ContainerUtil.addIfNotNull(result, contractWithConstraint(state, parameter, constraint, equality ? TRUE_VALUE : FALSE_VALUE)); - ContainerUtil.addIfNotNull(result, contractWithConstraint(state, parameter, negateConstraint(constraint), - equality ? FALSE_VALUE : TRUE_VALUE)); - } - } - return result; - } - return Collections.emptyList(); - } - - @Nullable - private IElementType getPrimitiveParameterType(int paramIndex) { - LighterASTNode typeElement = firstChildOfType(myTree, getParameters().get(paramIndex), TYPE); - LighterASTNode primitive = firstChildOfType(myTree, typeElement, ElementType.PRIMITIVE_TYPE_BIT_SET); - return primitive == null ? null : primitive.getTokenType(); - } - - static List toContracts(List states, ValueConstraint constraint) { - return ContainerUtil.map(states, state -> new MethodContract(state, constraint)); - } - - private List visitLogicalOperation(List operands, boolean conjunction, List states) { - ValueConstraint breakValue = conjunction ? FALSE_VALUE : TRUE_VALUE; - List finalStates = ContainerUtil.newArrayList(); - for (LighterASTNode operand : operands) { - List opResults = visitExpression(states, operand); - finalStates.addAll(ContainerUtil.filter(knownContracts(opResults), contract -> contract.returnValue == breakValue)); - states = antecedentsReturning(opResults, negateConstraint(breakValue)); - } - finalStates.addAll(toContracts(states, negateConstraint(breakValue))); - return finalStates; - } - - private static List knownContracts(List values) { - return ContainerUtil.mapNotNull(values, pc -> pc instanceof KnownContract ? ((KnownContract)pc).getContract() : null); - } - - private static List antecedentsReturning(List values, ValueConstraint result) { - return ContainerUtil.mapNotNull(knownContracts(values), contract -> contract.returnValue == result ? contract.arguments : null); - } - - private static class CodeBlockContracts { - List accumulated = new ArrayList<>(); - List varInitializers = new ArrayList<>(); - - void addAll(List contracts) { - if (contracts.isEmpty()) return; - - if (varInitializers.isEmpty()) { - accumulated.addAll(contracts); - } else { - accumulated.add(new SideEffectFilter(varInitializers, contracts)); - } - } - - void registerDeclaration(@NotNull LighterASTNode declStatement, @NotNull LighterAST tree, int scopeStart) { - for (LighterASTNode var : getChildrenOfType(tree, declStatement, LOCAL_VARIABLE)) { - LighterASTNode initializer = findExpressionChild(tree, var); - if (initializer != null) { - varInitializers.add(ExpressionRange.create(initializer, scopeStart)); - } - } - } - } - - @NotNull - private List visitStatements(List states, LighterASTNode... statements) { - CodeBlockContracts result = new CodeBlockContracts(); - for (LighterASTNode statement : statements) { - IElementType type = statement.getTokenType(); - if (type == BLOCK_STATEMENT) { - result.addAll(visitStatements(states, getStatements(getCodeBlock(statement)))); - } - else if (type == IF_STATEMENT) { - List conditionResults = visitExpression(states, findExpressionChild(myTree, statement)); - - LighterASTNode[] thenElse = getStatements(statement); - if (thenElse.length > 0) { - result.addAll(visitStatements(antecedentsReturning(conditionResults, TRUE_VALUE), thenElse[0])); - } - - List falseStates = antecedentsReturning(conditionResults, FALSE_VALUE); - if (thenElse.length > 1) { - result.addAll(visitStatements(falseStates, thenElse[1])); - } else { - states = falseStates; - continue; - } - } - else if (type == WHILE_STATEMENT) { - states = antecedentsReturning(visitExpression(states, findExpressionChild(myTree, statement)), FALSE_VALUE); - continue; - } - else if (type == THROW_STATEMENT) { - result.addAll(asPreContracts(toContracts(states, THROW_EXCEPTION))); - } - else if (type == RETURN_STATEMENT) { - result.addAll(visitExpression(states, findExpressionChild(myTree, statement))); - } - else if (type == ASSERT_STATEMENT) { - List conditionResults = visitExpression(states, findExpressionChild(myTree, statement)); - result.addAll(asPreContracts(toContracts(antecedentsReturning(conditionResults, FALSE_VALUE), THROW_EXCEPTION))); - } - else if (type == DECLARATION_STATEMENT) { - result.registerDeclaration(statement, myTree, myBody.getStartOffset()); - continue; - } - else if (type == DO_WHILE_STATEMENT) { - result.addAll(visitStatements(states, getStatements(statement))); - } - - break; // visit only the first statement unless it's 'if' whose 'then' always returns and the next statement is effectively 'else' - } - return result.accumulated; - } - - @Nullable - private ValueConstraint getLiteralConstraint(@Nullable LighterASTNode expr) { - if (expr != null && expr.getTokenType() == LITERAL_EXPRESSION) { - return getLiteralConstraint(myTree.getChildren(expr).get(0).getTokenType()); - } - return null; - } - - @NotNull - static ValueConstraint getLiteralConstraint(@NotNull IElementType literalTokenType) { - if (literalTokenType.equals(JavaTokenType.TRUE_KEYWORD)) return TRUE_VALUE; - if (literalTokenType.equals(JavaTokenType.FALSE_KEYWORD)) return FALSE_VALUE; - if (literalTokenType.equals(JavaTokenType.NULL_KEYWORD)) return NULL_VALUE; - return NOT_NULL_VALUE; - } - - static ValueConstraint negateConstraint(@NotNull ValueConstraint constraint) { - //noinspection EnumSwitchStatementWhichMissesCases - switch (constraint) { - case NULL_VALUE: return NOT_NULL_VALUE; - case NOT_NULL_VALUE: return NULL_VALUE; - case TRUE_VALUE: return FALSE_VALUE; - case FALSE_VALUE: return TRUE_VALUE; - } - return constraint; - } - - private int resolveParameter(@Nullable LighterASTNode expr) { - if (expr != null && expr.getTokenType() == REFERENCE_EXPRESSION && findExpressionChild(myTree, expr) == null) { - String name = JavaLightTreeUtil.getNameIdentifierText(myTree, expr); - if (name == null) return -1; - - List parameters = getParameters(); - for (int i = 0; i < parameters.size(); i++) { - if (name.equals(JavaLightTreeUtil.getNameIdentifierText(myTree, parameters.get(i)))) { - return i; - } - } - } - return -1; - } - - @Nullable - static ValueConstraint[] withConstraint(ValueConstraint[] constraints, int index, ValueConstraint constraint) { - if (constraints[index] == constraint) return constraints; - - ValueConstraint negated = negateConstraint(constraint); - if (negated != constraint && constraints[index] == negated) { - return null; - } - - ValueConstraint[] copy = constraints.clone(); - copy[index] = constraint; - return copy; - } - } \ No newline at end of file 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 new file mode 100644 index 000000000000..c4a139311de8 --- /dev/null +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/ContractInferenceInterpreter.java @@ -0,0 +1,400 @@ +/* + * 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.codeInspection.dataFlow.MethodContract.ValueConstraint; +import com.intellij.lang.LighterAST; +import com.intellij.lang.LighterASTNode; +import com.intellij.psi.JavaTokenType; +import com.intellij.psi.impl.source.JavaLightTreeUtil; +import com.intellij.psi.impl.source.tree.ElementType; +import com.intellij.psi.tree.IElementType; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.intellij.codeInspection.dataFlow.MethodContract.ValueConstraint.*; +import static com.intellij.psi.impl.source.JavaLightTreeUtil.findExpressionChild; +import static com.intellij.psi.impl.source.JavaLightTreeUtil.getExpressionChildren; +import static com.intellij.psi.impl.source.tree.JavaElementType.*; +import static com.intellij.psi.impl.source.tree.LightTreeUtil.firstChildOfType; +import static com.intellij.psi.impl.source.tree.LightTreeUtil.getChildrenOfType; + +class ContractInferenceInterpreter { + private final LighterAST myTree; + private final LighterASTNode myMethod; + private final LighterASTNode myBody; + + public ContractInferenceInterpreter(LighterAST tree, LighterASTNode method, LighterASTNode body) { + myTree = tree; + myMethod = method; + myBody = body; + } + + @NotNull + private List getParameters() { + LighterASTNode paramList = firstChildOfType(myTree, myMethod, PARAMETER_LIST); + return paramList != null ? getChildrenOfType(myTree, paramList, PARAMETER) : Collections.emptyList(); + } + + List inferContracts() { + LighterASTNode[] statements = getStatements(myBody); + if (statements.length == 0) return Collections.emptyList(); + + if (statements.length == 1) { + if (statements[0].getTokenType() == RETURN_STATEMENT) { + List result = handleDelegation(findExpressionChild(myTree, statements[0]), false); + if (result != null) { + return result; + } + } + else if (statements[0].getTokenType() == EXPRESSION_STATEMENT) { + LighterASTNode expr = findExpressionChild(myTree, statements[0]); + List result = expr != null && expr.getTokenType() == METHOD_CALL_EXPRESSION ? handleDelegation(expr, false) : null; + if (result != null) return result; + } + } + + return visitStatements(Collections.singletonList(MethodContract.createConstraintArray(getParameters().size())), statements); + } + + @Nullable + private LighterASTNode getCodeBlock(@Nullable LighterASTNode parent) { + return firstChildOfType(myTree, parent, CODE_BLOCK); + } + + @NotNull + private LighterASTNode[] getStatements(@Nullable LighterASTNode codeBlock) { + return codeBlock == null + ? LighterASTNode.EMPTY_ARRAY + : getChildrenOfType(myTree, codeBlock, ElementType.JAVA_STATEMENT_BIT_SET).toArray(LighterASTNode.EMPTY_ARRAY); + } + + @Nullable + private List handleDelegation(@Nullable LighterASTNode expression, boolean negated) { + if (expression == null) return null; + if (expression.getTokenType() == PARENTH_EXPRESSION) { + return handleDelegation(findExpressionChild(myTree, expression), negated); + } + + if (isNegationExpression(expression)) { + return handleDelegation(findExpressionChild(myTree, expression), !negated); + } + + if (expression.getTokenType() == METHOD_CALL_EXPRESSION) { + return Collections.singletonList(new DelegationContract(ExpressionRange.create(expression, myBody.getStartOffset()), negated)); + } + + return null; + } + + private boolean isNegationExpression(@Nullable LighterASTNode expression) { + return expression != null && expression.getTokenType() == PREFIX_EXPRESSION && firstChildOfType(myTree, expression, JavaTokenType.EXCL) != null; + } + + @NotNull + private List visitExpression(final List states, @Nullable LighterASTNode expr) { + if (expr == null) return Collections.emptyList(); + if (states.isEmpty()) return Collections.emptyList(); + if (states.size() > 300) return Collections.emptyList(); // too complex + + IElementType type = expr.getTokenType(); + if (type == POLYADIC_EXPRESSION || type == BINARY_EXPRESSION) { + List operands = getExpressionChildren(myTree, expr); + if (operands.size() == 2) { + boolean equality = firstChildOfType(myTree, expr, JavaTokenType.EQEQ) != null; + if (equality || firstChildOfType(myTree, expr, JavaTokenType.NE) != null) { + return asPreContracts(visitEqualityComparison(states, operands.get(0), operands.get(1), equality)); + } + } + boolean logicalAnd = firstChildOfType(myTree, expr, JavaTokenType.ANDAND) != null; + if (logicalAnd || firstChildOfType(myTree, expr, JavaTokenType.OROR) != null) { + return asPreContracts(visitLogicalOperation(operands, logicalAnd, states)); + } + } + + if (type == CONDITIONAL_EXPRESSION) { + List children = getExpressionChildren(myTree, expr); + if (children.size() != 3) return Collections.emptyList(); + + List conditionResults = visitExpression(states, children.get(0)); + return ContainerUtil.concat( + visitExpression(antecedentsReturning(conditionResults, TRUE_VALUE), children.get(1)), + visitExpression(antecedentsReturning(conditionResults, FALSE_VALUE), children.get(2))); + } + + + if (type == PARENTH_EXPRESSION) { + return visitExpression(states, findExpressionChild(myTree, expr)); + } + if (type == TYPE_CAST_EXPRESSION) { + return visitExpression(states, findExpressionChild(myTree, expr)); + } + + if (isNegationExpression(expr)) { + List result = ContainerUtil.newArrayList(); + for (PreContract contract : visitExpression(states, findExpressionChild(myTree, expr))) { + ContainerUtil.addIfNotNull(result, NegatingContract.negate(contract)); + } + return result; + } + + if (type == INSTANCE_OF_EXPRESSION) { + final int parameter = resolveParameter(findExpressionChild(myTree, expr)); + if (parameter >= 0) { + return asPreContracts(ContainerUtil.mapNotNull(states, state -> contractWithConstraint(state, parameter, NULL_VALUE, FALSE_VALUE))); + } + } + + if (type == NEW_EXPRESSION) { + return asPreContracts(toContracts(states, NOT_NULL_VALUE)); + } + if (type == METHOD_CALL_EXPRESSION) { + return Collections.singletonList(new MethodCallContract(ExpressionRange.create(expr, myBody.getStartOffset()), states)); + } + + final ValueConstraint constraint = getLiteralConstraint(expr); + if (constraint != null) { + return asPreContracts(toContracts(states, constraint)); + } + + int paramIndex = resolveParameter(expr); + if (paramIndex >= 0) { + 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])); + } else if (JavaTokenType.BOOLEAN_KEYWORD == getPrimitiveParameterType(paramIndex)) { + // if (boolValue) ... + ContainerUtil.addIfNotNull(result, contractWithConstraint(state, paramIndex, TRUE_VALUE, TRUE_VALUE)); + ContainerUtil.addIfNotNull(result, contractWithConstraint(state, paramIndex, FALSE_VALUE, FALSE_VALUE)); + } + } + return asPreContracts(result); + } + + return Collections.emptyList(); + } + + @NotNull + 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) { + ValueConstraint[] newState = withConstraint(state, parameter, paramConstraint); + return newState == null ? null : new MethodContract(newState, returnValue); + } + + private List visitEqualityComparison(List states, + LighterASTNode op1, + LighterASTNode op2, + boolean equality) { + int parameter = resolveParameter(op1); + ValueConstraint constraint = getLiteralConstraint(op2); + if (parameter < 0 || constraint == null) { + parameter = resolveParameter(op2); + constraint = getLiteralConstraint(op1); + } + if (parameter >= 0 && constraint != null) { + List result = ContainerUtil.newArrayList(); + for (ValueConstraint[] state : states) { + if (constraint == NOT_NULL_VALUE) { + if (getPrimitiveParameterType(parameter) == null) { + ContainerUtil.addIfNotNull(result, contractWithConstraint(state, parameter, NULL_VALUE, equality ? FALSE_VALUE : TRUE_VALUE)); + } + } else { + ContainerUtil.addIfNotNull(result, contractWithConstraint(state, parameter, constraint, equality ? TRUE_VALUE : FALSE_VALUE)); + ContainerUtil.addIfNotNull(result, contractWithConstraint(state, parameter, negateConstraint(constraint), + equality ? FALSE_VALUE : TRUE_VALUE)); + } + } + return result; + } + return Collections.emptyList(); + } + + @Nullable + private IElementType getPrimitiveParameterType(int paramIndex) { + LighterASTNode typeElement = firstChildOfType(myTree, getParameters().get(paramIndex), TYPE); + LighterASTNode primitive = firstChildOfType(myTree, typeElement, ElementType.PRIMITIVE_TYPE_BIT_SET); + return primitive == null ? null : primitive.getTokenType(); + } + + static List toContracts(List states, ValueConstraint constraint) { + return ContainerUtil.map(states, state -> new MethodContract(state, constraint)); + } + + private List visitLogicalOperation(List operands, boolean conjunction, List states) { + ValueConstraint breakValue = conjunction ? FALSE_VALUE : TRUE_VALUE; + List finalStates = ContainerUtil.newArrayList(); + for (LighterASTNode operand : operands) { + List opResults = visitExpression(states, operand); + finalStates.addAll(ContainerUtil.filter(knownContracts(opResults), contract -> contract.returnValue == breakValue)); + states = antecedentsReturning(opResults, negateConstraint(breakValue)); + } + finalStates.addAll(toContracts(states, negateConstraint(breakValue))); + return finalStates; + } + + private static List knownContracts(List values) { + return ContainerUtil.mapNotNull(values, pc -> pc instanceof KnownContract ? ((KnownContract)pc).getContract() : null); + } + + private static List antecedentsReturning(List values, ValueConstraint result) { + return ContainerUtil.mapNotNull(knownContracts(values), contract -> contract.returnValue == result ? contract.arguments : null); + } + + private static class CodeBlockContracts { + List accumulated = new ArrayList<>(); + List varInitializers = new ArrayList<>(); + + void addAll(List contracts) { + if (contracts.isEmpty()) return; + + if (varInitializers.isEmpty()) { + accumulated.addAll(contracts); + } else { + accumulated.add(new SideEffectFilter(varInitializers, contracts)); + } + } + + void registerDeclaration(@NotNull LighterASTNode declStatement, @NotNull LighterAST tree, int scopeStart) { + for (LighterASTNode var : getChildrenOfType(tree, declStatement, LOCAL_VARIABLE)) { + LighterASTNode initializer = findExpressionChild(tree, var); + if (initializer != null) { + varInitializers.add(ExpressionRange.create(initializer, scopeStart)); + } + } + } + } + + @NotNull + private List visitStatements(List states, LighterASTNode... statements) { + CodeBlockContracts result = new CodeBlockContracts(); + for (LighterASTNode statement : statements) { + IElementType type = statement.getTokenType(); + if (type == BLOCK_STATEMENT) { + result.addAll(visitStatements(states, getStatements(getCodeBlock(statement)))); + } + else if (type == IF_STATEMENT) { + List conditionResults = visitExpression(states, findExpressionChild(myTree, statement)); + + LighterASTNode[] thenElse = getStatements(statement); + if (thenElse.length > 0) { + result.addAll(visitStatements(antecedentsReturning(conditionResults, TRUE_VALUE), thenElse[0])); + } + + List falseStates = antecedentsReturning(conditionResults, FALSE_VALUE); + if (thenElse.length > 1) { + result.addAll(visitStatements(falseStates, thenElse[1])); + } else { + states = falseStates; + continue; + } + } + else if (type == WHILE_STATEMENT) { + states = antecedentsReturning(visitExpression(states, findExpressionChild(myTree, statement)), FALSE_VALUE); + continue; + } + else if (type == THROW_STATEMENT) { + result.addAll(asPreContracts(toContracts(states, THROW_EXCEPTION))); + } + else if (type == RETURN_STATEMENT) { + result.addAll(visitExpression(states, findExpressionChild(myTree, statement))); + } + else if (type == ASSERT_STATEMENT) { + List conditionResults = visitExpression(states, findExpressionChild(myTree, statement)); + result.addAll(asPreContracts(toContracts(antecedentsReturning(conditionResults, FALSE_VALUE), THROW_EXCEPTION))); + } + else if (type == DECLARATION_STATEMENT) { + result.registerDeclaration(statement, myTree, myBody.getStartOffset()); + continue; + } + else if (type == DO_WHILE_STATEMENT) { + result.addAll(visitStatements(states, getStatements(statement))); + } + + break; // visit only the first statement unless it's 'if' whose 'then' always returns and the next statement is effectively 'else' + } + return result.accumulated; + } + + @Nullable + private ValueConstraint getLiteralConstraint(@Nullable LighterASTNode expr) { + if (expr != null && expr.getTokenType() == LITERAL_EXPRESSION) { + return getLiteralConstraint(myTree.getChildren(expr).get(0).getTokenType()); + } + return null; + } + + @NotNull + static ValueConstraint getLiteralConstraint(@NotNull IElementType literalTokenType) { + if (literalTokenType.equals(JavaTokenType.TRUE_KEYWORD)) return TRUE_VALUE; + if (literalTokenType.equals(JavaTokenType.FALSE_KEYWORD)) return FALSE_VALUE; + if (literalTokenType.equals(JavaTokenType.NULL_KEYWORD)) return NULL_VALUE; + return NOT_NULL_VALUE; + } + + static ValueConstraint negateConstraint(@NotNull ValueConstraint constraint) { + //noinspection EnumSwitchStatementWhichMissesCases + switch (constraint) { + case NULL_VALUE: return NOT_NULL_VALUE; + case NOT_NULL_VALUE: return NULL_VALUE; + case TRUE_VALUE: return FALSE_VALUE; + case FALSE_VALUE: return TRUE_VALUE; + } + return constraint; + } + + private int resolveParameter(@Nullable LighterASTNode expr) { + if (expr != null && expr.getTokenType() == REFERENCE_EXPRESSION && findExpressionChild(myTree, expr) == null) { + String name = JavaLightTreeUtil.getNameIdentifierText(myTree, expr); + if (name == null) return -1; + + List parameters = getParameters(); + for (int i = 0; i < parameters.size(); i++) { + if (name.equals(JavaLightTreeUtil.getNameIdentifierText(myTree, parameters.get(i)))) { + return i; + } + } + } + return -1; + } + + @Nullable + static ValueConstraint[] withConstraint(ValueConstraint[] constraints, int index, ValueConstraint constraint) { + if (constraints[index] == constraint) return constraints; + + ValueConstraint negated = negateConstraint(constraint); + if (negated != constraint && constraints[index] == negated) { + return null; + } + + ValueConstraint[] copy = constraints.clone(); + copy[index] = constraint; + return copy; + } + +} \ No newline at end of file