From bae3ff52116960e15c4754ffb91d81bf47c20f0c Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Tue, 23 Oct 2018 11:23:06 +0300 Subject: [PATCH] Don't try parsing contract-like function not from `kotlin` package Returning `null` from `doCheckContract` functions means that we have failed to parse contract function and should report an error, but if the called function isn't true contract, we shouldn't evaluate that code at all. #KT-27758 Fixed --- .../parsing/ContractParsingServices.kt | 30 +++++++++---- .../dsl/callUsualContractFunction.kt | 42 +++++++++++++++++++ .../dsl/callUsualContractFunction.txt | 21 ++++++++++ .../DiagnosticsTestWithStdLibGenerated.java | 5 +++ ...ticsTestWithStdLibUsingJavacGenerated.java | 5 +++ 5 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt create mode 100644 compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/contracts/parsing/ContractParsingServices.kt b/compiler/frontend/src/org/jetbrains/kotlin/contracts/parsing/ContractParsingServices.kt index c18601a4e94..b57fd3470a8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/contracts/parsing/ContractParsingServices.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/contracts/parsing/ContractParsingServices.kt @@ -34,29 +34,43 @@ import org.jetbrains.kotlin.resolve.scopes.LexicalScope import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind class ContractParsingServices(val languageVersionSettings: LanguageVersionSettings) { + /** + * ! IMPORTANT NOTICE ! + * + * This function has very important non-obvious implicit contract: + * it *must* call [org.jetbrains.kotlin.contracts.description.LazyContractProvider.setContractDescription] + * if FunctionDescriptor had [LazyContractProvider] in the user data. + * + * Otherwise, it may lead to inconsistent resolve state and failed assertions + */ fun checkContractAndRecordIfPresent(expression: KtExpression, trace: BindingTrace, scope: LexicalScope, isFirstStatement: Boolean) { - if (!expression.isContractDescriptionCallPsiCheck()) return // fastpath + // Fastpath. Note that it doesn't violates invariant described in KDoc, because 'isContractDescriptionCallPsiCheck' + // is a *necessary* (but not sufficient, actually) condition for presence of 'LazyContractProvider' + if (!expression.isContractDescriptionCallPsiCheck()) return val collector = TraceBasedCollector(trace, expression) val callContext = ContractCallContext(expression, isFirstStatement, scope, trace.bindingContext) + val contractProviderIfAny = (scope.ownerDescriptor as? FunctionDescriptor)?.getUserData(ContractProviderKey) + + if (!callContext.isContractDescriptionCallPreciseCheck()) { + contractProviderIfAny?.setContractDescription(null) + return + } + val parsedContract = doCheckContract(collector, callContext) collector.flushDiagnostics(parsingFailed = parsedContract == null) - val contractProviderIfAny = (scope.ownerDescriptor as? FunctionDescriptor)?.getUserData(ContractProviderKey) - if (collector.hasErrors()) contractProviderIfAny?.setContractDescription(null) else contractProviderIfAny?.setContractDescription(parsedContract) } + private fun ContractCallContext.isContractDescriptionCallPreciseCheck(): Boolean = + contractCallExpression.isContractDescriptionCallPreciseCheck(bindingContext) + private fun doCheckContract(collector: ContractParsingDiagnosticsCollector, callContext: ContractCallContext): ContractDescription? { - val expression = callContext.contractCallExpression - val bindingContext = callContext.bindingContext - - if (!expression.isContractDescriptionCallPreciseCheck(bindingContext)) return null - checkFeatureEnabled(collector) checkContractAllowedHere(collector, callContext) diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt new file mode 100644 index 00000000000..1c03aa81914 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt @@ -0,0 +1,42 @@ +// !DIAGNOSTICS: -UNUSED_PARAMETER + +package my + +// Accepts block, can't be distinguished from kotlin.contract without resolve +fun contract(block: () -> Unit) {} + +// Accepts int, potentially can be distinguished from kotlin.contract by PSI, +// but as of Kotlin 1.3.0, we don't do that +fun contract(i: Int) {} + +fun doStuff() {} + +class SomeClass { + + fun contract() {} + + fun callMemberContractWithThis() { + this.contract() + } + + fun callMemberContractWithoutThis() { + contract() + } + + fun callTopLevelSamePsiInMember() { + contract { } + } +} + +fun callTopLevelSamePsi() { + contract { } +} + +fun callTopLevelDifferentPsi() { + contract(42) +} + +fun callTopLevelSamePsiNotFirstStatement() { + doStuff() + contract { } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.txt b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.txt new file mode 100644 index 00000000000..2f677496867 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.txt @@ -0,0 +1,21 @@ +package + +package my { + public fun callTopLevelDifferentPsi(): kotlin.Unit + public fun callTopLevelSamePsi(): kotlin.Unit + public fun callTopLevelSamePsiNotFirstStatement(): kotlin.Unit + public fun contract(/*0*/ block: () -> kotlin.Unit): kotlin.Unit + public fun contract(/*0*/ i: kotlin.Int): kotlin.Unit + public fun doStuff(): kotlin.Unit + + public final class SomeClass { + public constructor SomeClass() + public final fun callMemberContractWithThis(): kotlin.Unit + public final fun callMemberContractWithoutThis(): kotlin.Unit + public final fun callTopLevelSamePsiInMember(): kotlin.Unit + public final fun contract(): kotlin.Unit + public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean + public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int + public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java index e3cd17456e2..e33b001cff7 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsTestWithStdLibGenerated.java @@ -1112,6 +1112,11 @@ public class DiagnosticsTestWithStdLibGenerated extends AbstractDiagnosticsTestW KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("callUsualContractFunction.kt") + public void testCallUsualContractFunction() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt"); + } + @TestMetadata("useBeforeDeclaration.kt") public void testUseBeforeDeclaration() throws Exception { runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/useBeforeDeclaration.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java index 3a0e905b183..f54b6e99804 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/javac/DiagnosticsTestWithStdLibUsingJavacGenerated.java @@ -1112,6 +1112,11 @@ public class DiagnosticsTestWithStdLibUsingJavacGenerated extends AbstractDiagno KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); } + @TestMetadata("callUsualContractFunction.kt") + public void testCallUsualContractFunction() throws Exception { + runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/callUsualContractFunction.kt"); + } + @TestMetadata("useBeforeDeclaration.kt") public void testUseBeforeDeclaration() throws Exception { runTest("compiler/testData/diagnostics/testsWithStdLib/contracts/dsl/useBeforeDeclaration.kt");