Forbid val field initialization inside EXACLTY_ONCE lambda

unless the lambda is inline. This way, final field will remain final.
This commit is contained in:
Ilmir Usmanov
2020-06-03 18:32:43 +02:00
parent 6d6a228057
commit 8cc5f2abfb
18 changed files with 177 additions and 76 deletions
@@ -422,14 +422,6 @@ public class PropertyCodegen {
} }
modifiers |= getVisibilityForBackingField(propertyDescriptor, isDelegate); modifiers |= getVisibilityForBackingField(propertyDescriptor, isDelegate);
// If val is initialized in EXACTLY_ONCE closure, other class from the same package initializes it
// so, its visibility should be package private and not final
if (!propertyDescriptor.isVar() &&
bindingContext.get(BindingContext.FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE, propertyDescriptor) != null
) {
modifiers &= ~(ACC_PRIVATE | ACC_FINAL);
}
if (AsmUtil.isPropertyWithBackingFieldCopyInOuterClass(propertyDescriptor)) { if (AsmUtil.isPropertyWithBackingFieldCopyInOuterClass(propertyDescriptor)) {
ImplementationBodyCodegen parentBodyCodegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen(); ImplementationBodyCodegen parentBodyCodegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen();
parentBodyCodegen.addCompanionObjectPropertyToCopy(propertyDescriptor, defaultValue); parentBodyCodegen.addCompanionObjectPropertyToCopy(propertyDescriptor, defaultValue);
@@ -3816,6 +3816,11 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirOldFronte
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt"); runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt");
} }
@TestMetadata("fieldInitialization.kt")
public void testFieldInitialization() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt");
}
@TestMetadata("infiniteLoops.kt") @TestMetadata("infiniteLoops.kt")
public void testInfiniteLoops() throws Exception { public void testInfiniteLoops() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt"); runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt");
@@ -5097,11 +5097,6 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/contracts/exception.kt"); runTest("compiler/testData/codegen/box/contracts/exception.kt");
} }
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt") @TestMetadata("forLoop.kt")
public void testForLoop() throws Exception { public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt"); runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
@@ -179,7 +179,6 @@ public interface BindingContext {
WritableSlice<KtElement, Boolean> USED_AS_RESULT_OF_LAMBDA = Slices.createSimpleSetSlice(); WritableSlice<KtElement, Boolean> USED_AS_RESULT_OF_LAMBDA = Slices.createSimpleSetSlice();
WritableSlice<VariableDescriptor, CaptureKind> CAPTURED_IN_CLOSURE = new BasicWritableSlice<>(DO_NOTHING); WritableSlice<VariableDescriptor, CaptureKind> CAPTURED_IN_CLOSURE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<PropertyDescriptor, Boolean> FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<KtDeclaration, PreliminaryDeclarationVisitor> PRELIMINARY_VISITOR = new BasicWritableSlice<>(DO_NOTHING); WritableSlice<KtDeclaration, PreliminaryDeclarationVisitor> PRELIMINARY_VISITOR = new BasicWritableSlice<>(DO_NOTHING);
WritableSlice<Box<DeferredType>, Boolean> DEFERRED_TYPE = Slices.createCollectiveSetSlice(); WritableSlice<Box<DeferredType>, Boolean> DEFERRED_TYPE = Slices.createCollectiveSetSlice();
@@ -13,10 +13,10 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Errors.CAPTURED_VAL_INITIALIZATION
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext.CAPTURED_IN_CLOSURE import org.jetbrains.kotlin.resolve.BindingContext.CAPTURED_IN_CLOSURE
import org.jetbrains.kotlin.resolve.BindingContext.FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE
import org.jetbrains.kotlin.resolve.BindingTrace import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
@@ -35,6 +35,7 @@ class CapturingInClosureChecker : CallChecker {
val variableDescriptor = variableResolvedCall.resultingDescriptor as? VariableDescriptor val variableDescriptor = variableResolvedCall.resultingDescriptor as? VariableDescriptor
if (variableDescriptor != null) { if (variableDescriptor != null) {
checkCapturingInClosure(variableDescriptor, context.trace, context.scope) checkCapturingInClosure(variableDescriptor, context.trace, context.scope)
checkFieldInExactlyOnceLambdaInitialization(variableDescriptor, context.trace, context.scope.ownerDescriptor, reportOn)
} }
} }
@@ -47,12 +48,23 @@ class CapturingInClosureChecker : CallChecker {
return return
} }
} }
// Check whether a field is captured in EXACTLY_ONCE contract }
if (variable !is PropertyDescriptor || scopeContainer !is AnonymousFunctionDescriptor) return
private fun checkFieldInExactlyOnceLambdaInitialization(
variable: VariableDescriptor,
trace: BindingTrace,
scopeContainer: DeclarationDescriptor,
reportOn: PsiElement
) {
if (variable !is PropertyDescriptor || scopeContainer !is AnonymousFunctionDescriptor || variable.isVar) return
val scopeDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(scopeContainer) as? KtFunction ?: return val scopeDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(scopeContainer) as? KtFunction ?: return
if (scopeContainer.containingDeclaration !is ConstructorDescriptor) return if (scopeContainer.containingDeclaration !is ConstructorDescriptor) return
if (isExactlyOnceContract(trace.bindingContext, scopeDeclaration)) { if (!isExactlyOnceContract(trace.bindingContext, scopeDeclaration)) return
trace.record(FIELD_CAPTURED_IN_EXACLY_ONCE_CLOSURE, variable) if (trace.bindingContext[CAPTURED_IN_CLOSURE, variable] == CaptureKind.NOT_INLINE) return
val (callee, param) = getCalleeDescriptorAndParameter(trace.bindingContext, scopeDeclaration) ?: return
if (callee !is FunctionDescriptor) return
if (!callee.isInline || (param.isCrossinline || !InlineUtil.isInlineParameter(param))) {
trace.report(CAPTURED_VAL_INITIALIZATION.on(reportOn as KtExpression, variable))
} }
} }
-26
View File
@@ -1,26 +0,0 @@
// !USE_EXPERIMENTAL: kotlin.contracts.ExperimentalContracts
// IGNORE_BACKEND: NATIVE
// WITH_RUNTIME
// IGNORE_LIGHT_ANALYSIS
import kotlin.contracts.*
fun runOnce(action: () -> Unit) {
contract {
callsInPlace(action, InvocationKind.EXACTLY_ONCE)
}
action()
}
class Foo {
val res: String
init {
runOnce {
res = "OK"
}
}
}
fun box(): String {
return Foo().res
}
@@ -0,0 +1,60 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
// !USE_EXPERIMENTAL: kotlin.internal.ContractsDsl
import kotlin.contracts.*
@kotlin.contracts.ExperimentalContracts
inline fun inlineMe(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@kotlin.contracts.ExperimentalContracts
inline fun crossinlineMe(crossinline block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@Suppress("NOTHING_TO_INLINE")
@kotlin.contracts.ExperimentalContracts
inline fun noinlineMe(noinline block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@kotlin.contracts.ExperimentalContracts
fun notinline(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@kotlin.contracts.ExperimentalContracts
class Test {
val a: String
val b: String
val c: String
val d: String
init {
inlineMe {
a = "allowed"
}
crossinlineMe {
b = "not allowed"
}
noinlineMe {
c = "not allowed"
}
notinline {
d = "not allowed"
}
}
}
@@ -0,0 +1,60 @@
// !LANGUAGE: +AllowContractsForCustomFunctions +UseCallsInPlaceEffect
// !USE_EXPERIMENTAL: kotlin.internal.ContractsDsl
import kotlin.contracts.*
@kotlin.contracts.ExperimentalContracts
inline fun inlineMe(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@kotlin.contracts.ExperimentalContracts
inline fun crossinlineMe(crossinline block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@Suppress("NOTHING_TO_INLINE")
@kotlin.contracts.ExperimentalContracts
inline fun noinlineMe(noinline block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@kotlin.contracts.ExperimentalContracts
fun notinline(block: () -> Unit) {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
}
@kotlin.contracts.ExperimentalContracts
class Test {
val a: String
val b: String
val c: String
val d: String
init {
inlineMe {
a = "allowed"
}
crossinlineMe {
<!CAPTURED_VAL_INITIALIZATION!>b<!> = "not allowed"
}
noinlineMe {
<!CAPTURED_VAL_INITIALIZATION!>c<!> = "not allowed"
}
notinline {
<!CAPTURED_VAL_INITIALIZATION!>d<!> = "not allowed"
}
}
}
@@ -0,0 +1,24 @@
package
@kotlin.contracts.ExperimentalContracts public inline fun crossinlineMe(/*0*/ crossinline block: () -> kotlin.Unit): kotlin.Unit
CallsInPlace(block, EXACTLY_ONCE)
@kotlin.contracts.ExperimentalContracts public inline fun inlineMe(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
CallsInPlace(block, EXACTLY_ONCE)
@kotlin.Suppress(names = {"NOTHING_TO_INLINE"}) @kotlin.contracts.ExperimentalContracts public inline fun noinlineMe(/*0*/ noinline block: () -> kotlin.Unit): kotlin.Unit
CallsInPlace(block, EXACTLY_ONCE)
@kotlin.contracts.ExperimentalContracts public fun notinline(/*0*/ block: () -> kotlin.Unit): kotlin.Unit
CallsInPlace(block, EXACTLY_ONCE)
@kotlin.contracts.ExperimentalContracts public final class Test {
public constructor Test()
public final val a: kotlin.String
public final val b: kotlin.String
public final val c: kotlin.String
public final val d: kotlin.String
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
}
@@ -86,7 +86,7 @@ class DefiniteInitializationInInitSection {
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val y: Int<!> <!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val y: Int<!>
init { init {
myRun { x = 42 } myRun { <!CAPTURED_VAL_INITIALIZATION!>x<!> = 42 }
unknownRun { <!CAPTURED_MEMBER_VAL_INITIALIZATION!>y<!> = 239 } unknownRun { <!CAPTURED_MEMBER_VAL_INITIALIZATION!>y<!> = 239 }
} }
} }
@@ -3823,6 +3823,11 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTestWithFirVali
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt"); runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt");
} }
@TestMetadata("fieldInitialization.kt")
public void testFieldInitialization() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt");
}
@TestMetadata("infiniteLoops.kt") @TestMetadata("infiniteLoops.kt")
public void testInfiniteLoops() throws Exception { public void testInfiniteLoops() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt"); runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt");
@@ -3818,6 +3818,11 @@ public class DiagnosticsUsingJavacTestGenerated extends AbstractDiagnosticsUsing
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt"); runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldAsClassDelegate.kt");
} }
@TestMetadata("fieldInitialization.kt")
public void testFieldInitialization() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/fieldInitialization.kt");
}
@TestMetadata("infiniteLoops.kt") @TestMetadata("infiniteLoops.kt")
public void testInfiniteLoops() throws Exception { public void testInfiniteLoops() throws Exception {
runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt"); runTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/infiniteLoops.kt");
@@ -5127,11 +5127,6 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/contracts/exception.kt"); runTest("compiler/testData/codegen/box/contracts/exception.kt");
} }
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt") @TestMetadata("forLoop.kt")
public void testForLoop() throws Exception { public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt"); runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
@@ -5127,11 +5127,6 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/contracts/exception.kt"); runTest("compiler/testData/codegen/box/contracts/exception.kt");
} }
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt") @TestMetadata("forLoop.kt")
public void testForLoop() throws Exception { public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt"); runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
@@ -5097,11 +5097,6 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/contracts/exception.kt"); runTest("compiler/testData/codegen/box/contracts/exception.kt");
} }
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt") @TestMetadata("forLoop.kt")
public void testForLoop() throws Exception { public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt"); runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
@@ -4127,11 +4127,6 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
runTest("compiler/testData/codegen/box/contracts/exception.kt"); runTest("compiler/testData/codegen/box/contracts/exception.kt");
} }
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt") @TestMetadata("forLoop.kt")
public void testForLoop() throws Exception { public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt"); runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
@@ -4137,11 +4137,6 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/contracts/exception.kt"); runTest("compiler/testData/codegen/box/contracts/exception.kt");
} }
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt") @TestMetadata("forLoop.kt")
public void testForLoop() throws Exception { public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt"); runTest("compiler/testData/codegen/box/contracts/forLoop.kt");
@@ -4137,11 +4137,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/contracts/exception.kt"); runTest("compiler/testData/codegen/box/contracts/exception.kt");
} }
@TestMetadata("field.kt")
public void testField() throws Exception {
runTest("compiler/testData/codegen/box/contracts/field.kt");
}
@TestMetadata("forLoop.kt") @TestMetadata("forLoop.kt")
public void testForLoop() throws Exception { public void testForLoop() throws Exception {
runTest("compiler/testData/codegen/box/contracts/forLoop.kt"); runTest("compiler/testData/codegen/box/contracts/forLoop.kt");