Fix recursion in contract declaration analysis. KT-26386 Fixed

Issue fixed only in old inference
This commit is contained in:
Dmitriy Novozhilov
2019-03-07 14:13:28 +03:00
parent d0462859bf
commit 77c98bef4d
13 changed files with 137 additions and 14 deletions
@@ -33,6 +33,7 @@ import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -127,6 +128,10 @@ class EffectSystem(
bindingTrace: BindingTrace,
moduleDescriptor: ModuleDescriptor
): MutableContextInfo {
val isInContractBlock = expression.parentsWithSelf.filterIsInstance<KtExpression>().any {
bindingTrace.bindingContext[BindingContext.IS_CONTRACT_DECLARATION_BLOCK, it] == true
}
if (isInContractBlock) return MutableContextInfo.EMPTY
val computation = getNonTrivialComputation(expression, bindingTrace, moduleDescriptor) ?: return MutableContextInfo.EMPTY
return InfoCollector(observedEffect, constants).collectFromSchema(computation.effects)
}
@@ -99,6 +99,7 @@ public interface BindingContext {
WritableSlice<KtExpression, KotlinType> EXPECTED_EXPRESSION_TYPE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtElement, Computation> EXPRESSION_EFFECTS = Slices.createSimpleSlice();
WritableSlice<KtElement, Boolean> CONTRACT_NOT_ALLOWED = Slices.createSimpleSlice();
WritableSlice<KtElement, Boolean> IS_CONTRACT_DECLARATION_BLOCK = Slices.createSimpleSlice();
WritableSlice<KtFunction, KotlinType> EXPECTED_RETURN_TYPE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtExpression, DataFlowInfo> DATAFLOW_INFO_AFTER_CONDITION = Slices.createSimpleSlice();
WritableSlice<VariableDescriptor, DataFlowValue> BOUND_INITIALIZER_VALUE = Slices.createSimpleSlice();
@@ -9,12 +9,18 @@ import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
import org.jetbrains.kotlin.builtins.isFunctionOrSuspendFunctionType
import org.jetbrains.kotlin.contracts.EffectSystem
import org.jetbrains.kotlin.contracts.description.ContractProviderKey
import org.jetbrains.kotlin.contracts.description.LazyContractProvider
import org.jetbrains.kotlin.contracts.parsing.isContractCallDescriptor
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.isContractDescriptionCallPsiCheck
import org.jetbrains.kotlin.psi.psiUtil.isFirstStatement
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.BindingContext.CONSTRAINT_SYSTEM_COMPLETER
import org.jetbrains.kotlin.resolve.calls.callResolverUtil.ResolveArgumentsMode.RESOLVE_FUNCTION_ARGUMENTS
@@ -129,6 +135,20 @@ class CallCompleter(
context: BasicCallResolutionContext,
tracing: TracingStrategy
) {
context.call.callElement.safeAs<KtExpression>()?.let { callExpression ->
if (callExpression.isFirstStatement() && callExpression.isContractDescriptionCallPsiCheck()) {
if (resolvedCall?.resultingDescriptor?.isContractCallDescriptor() != true) {
context.scope.ownerDescriptor
.safeAs<FunctionDescriptor>()
?.getUserData(ContractProviderKey)
?.safeAs<LazyContractProvider>()
?.setContractDescription(null)
} else {
context.trace.record(BindingContext.IS_CONTRACT_DECLARATION_BLOCK, callExpression, true)
}
}
}
if (resolvedCall == null || resolvedCall.isCompleted || resolvedCall.constraintSystem == null) {
completeArguments(context, results)
resolvedCall?.updateResultDataFlowInfoUsingEffects(context.trace)
@@ -12,9 +12,8 @@ import org.jetbrains.kotlin.descriptors.PackageFragmentDescriptor
import org.jetbrains.kotlin.descriptors.PropertyAccessorDescriptor
import org.jetbrains.kotlin.descriptors.isOverridable
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.psiUtil.isFirstStatement
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
@@ -57,13 +56,7 @@ object ContractNotAllowedCallChecker : CallChecker {
if (functionDescriptor?.isOverridable == true) contractNotAllowed("Contracts are not allowed for open functions")
var parent = callElement.parent
var element = callElement
if (parent is KtDotQualifiedExpression) {
element = parent
parent = parent.parent
}
if ((parent !is KtBlockExpression || parent.children.first { it is KtElement } != element) && inFunctionBodyBlock) {
if (!callElement.isFirstStatement() && inFunctionBodyBlock) {
contractNotAllowed("Contract should be the first statement")
}
@@ -315,6 +315,16 @@ fun KtQualifiedExpression.isContractDescriptionCallPsiCheck(): Boolean {
return receiverExpression.text == "kotlin.contracts" && expression.isContractDescriptionCallPsiCheck()
}
fun KtElement.isFirstStatement(): Boolean {
var parent = parent
var element = this
if (parent is KtDotQualifiedExpression) {
element = parent
parent = parent.parent
}
return parent is KtBlockExpression && parent.children.first { it is KtElement } == element
}
// ----------- Other -----------------------------------------------------------------------------------------------------------------------
@@ -0,0 +1,25 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// !LANGUAGE: +UseReturnsEffect
// Issue: KT-26386
import kotlin.contracts.*
fun case_1(): Boolean {
contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION!>case_1()<!> }
return true
}
fun case_2(): Boolean {
contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION!>case_3()<!> }
return true
}
fun case_3(): Boolean {
contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION!>case_2()<!> }
return true
}
fun case_4(): Boolean {
kotlin.contracts.contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION!>case_1()<!> }
return true
}
@@ -0,0 +1,6 @@
package
public fun case_1(): kotlin.Boolean
public fun case_2(): kotlin.Boolean
public fun case_3(): kotlin.Boolean
public fun case_4(): kotlin.Boolean
@@ -0,0 +1,39 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// !LANGUAGE: +UseReturnsEffect
// Issue: KT-26386
fun myRun(block: () -> Unit) {
block()
}
fun contract(block: () -> Unit) {
block()
}
fun case_1(): Boolean? {
contract { case_1() }
return null
}
fun case_2(): Boolean? {
contract { case_3() }
return null
}
fun case_3(): Boolean? {
contract { case_2() }
return null
}
fun case4() {
contract {
myRun {
val s: String
run {
s = "hello"
}
s.length
}
}
}
@@ -0,0 +1,8 @@
package
public fun case4(): kotlin.Unit
public fun case_1(): kotlin.Boolean?
public fun case_2(): kotlin.Boolean?
public fun case_3(): kotlin.Boolean?
public fun contract(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
public fun myRun(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
@@ -1,2 +0,0 @@
CASES: 4
java.lang.StackOverflowError
@@ -31,12 +31,10 @@ fun case_3(): Boolean {
/*
* TESTCASE NUMBER: 4
* UNEXPECTED BEHAVIOUR
* ISSUES: KT-26386
* EXCEPTION: compiler
*/
fun case_4(): Boolean? {
contract { returns(null) implies case_4() }
contract { returns(null) implies <!ERROR_IN_CONTRACT_DESCRIPTION, TYPE_MISMATCH!>case_4()<!> }
return null
}
@@ -1209,6 +1209,16 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/notFirstStatement.kt");
}
@TestMetadata("recursiveContract.kt")
public void testRecursiveContract() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.kt");
}
@TestMetadata("recursiveContractCustomContractFunction.kt")
public void testRecursiveContractCustomContractFunction() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.kt");
}
@TestMetadata("referenceToProperty.kt")
public void testReferenceToProperty() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/referenceToProperty.kt");
@@ -1209,6 +1209,16 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/notFirstStatement.kt");
}
@TestMetadata("recursiveContract.kt")
public void testRecursiveContract() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContract.kt");
}
@TestMetadata("recursiveContractCustomContractFunction.kt")
public void testRecursiveContractCustomContractFunction() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/recursiveContractCustomContractFunction.kt");
}
@TestMetadata("referenceToProperty.kt")
public void testReferenceToProperty() throws Exception {
runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/errors/referenceToProperty.kt");