diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.kt index 16a570addb6..ead8c09633a 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeVariablesData.kt @@ -26,16 +26,35 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.WriteValueInstructi import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction import org.jetbrains.kotlin.cfg.pseudocodeTraverser.Edges import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder +import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse +import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.VariableDescriptor -import org.jetbrains.kotlin.psi.KtProperty +import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContextUtils.variableDescriptorForDeclaration -import java.util.* +import org.jetbrains.kotlin.utils.addToStdlib.safeAs + +private typealias ImmutableSet = javaslang.collection.Set +private typealias ImmutableHashSet = javaslang.collection.HashSet class PseudocodeVariablesData(val pseudocode: Pseudocode, private val bindingContext: BindingContext) { private val pseudocodeVariableDataCollector = PseudocodeVariableDataCollector(bindingContext, pseudocode) - private val declaredVariablesForDeclaration = hashMapOf>() + private class VariablesForDeclaration( + val valsWithTrivialInitializer: Set, + val nonTrivialVariables: Set + ) { + val allVars = + if (nonTrivialVariables.isEmpty()) + valsWithTrivialInitializer + else + LinkedHashSet(valsWithTrivialInitializer).also { it.addAll(nonTrivialVariables) } + } + + private val declaredVariablesForDeclaration = hashMapOf() + private val rootVariables by lazy(LazyThreadSafetyMode.NONE) { + getAllDeclaredVariables(pseudocode, includeInsideLocalDeclarations = true) + } val variableInitializers: Map> by lazy { computeVariableInitializers() @@ -44,49 +63,87 @@ class PseudocodeVariablesData(val pseudocode: Pseudocode, private val bindingCon val blockScopeVariableInfo: BlockScopeVariableInfo get() = pseudocodeVariableDataCollector.blockScopeVariableInfo - fun getDeclaredVariables(pseudocode: Pseudocode, includeInsideLocalDeclarations: Boolean): Set { + fun getDeclaredVariables(pseudocode: Pseudocode, includeInsideLocalDeclarations: Boolean): Set = + getAllDeclaredVariables(pseudocode, includeInsideLocalDeclarations).allVars + + private fun getAllDeclaredVariables(pseudocode: Pseudocode, includeInsideLocalDeclarations: Boolean): VariablesForDeclaration { if (!includeInsideLocalDeclarations) { return getUpperLevelDeclaredVariables(pseudocode) } - val declaredVariables = linkedSetOf() - declaredVariables.addAll(getUpperLevelDeclaredVariables(pseudocode)) + val nonTrivialVariables = linkedSetOf() + val valsWithTrivialInitializer = linkedSetOf() + addVariablesFromPseudocode(pseudocode, nonTrivialVariables, valsWithTrivialInitializer) for (localFunctionDeclarationInstruction in pseudocode.localDeclarations) { val localPseudocode = localFunctionDeclarationInstruction.body - declaredVariables.addAll(getUpperLevelDeclaredVariables(localPseudocode)) + addVariablesFromPseudocode(localPseudocode, nonTrivialVariables, valsWithTrivialInitializer) } - return declaredVariables + return VariablesForDeclaration(valsWithTrivialInitializer, nonTrivialVariables) } - private fun getUpperLevelDeclaredVariables(pseudocode: Pseudocode): Set { - var declaredVariables = declaredVariablesForDeclaration[pseudocode] - if (declaredVariables == null) { - declaredVariables = computeDeclaredVariablesForPseudocode(pseudocode) - declaredVariablesForDeclaration.put(pseudocode, declaredVariables) + private fun addVariablesFromPseudocode( + pseudocode: Pseudocode, + nonTrivialVariables: MutableSet, + valsWithTrivialInitializer: MutableSet + ) { + getUpperLevelDeclaredVariables(pseudocode).let { + nonTrivialVariables.addAll(it.nonTrivialVariables) + valsWithTrivialInitializer.addAll(it.valsWithTrivialInitializer) } - return declaredVariables } - private fun computeDeclaredVariablesForPseudocode(pseudocode: Pseudocode): Set { - val declaredVariables = linkedSetOf() + private fun getUpperLevelDeclaredVariables(pseudocode: Pseudocode) = declaredVariablesForDeclaration.getOrPut(pseudocode) { + computeDeclaredVariablesForPseudocode(pseudocode) + } + + private fun computeDeclaredVariablesForPseudocode(pseudocode: Pseudocode): VariablesForDeclaration { + val valsWithTrivialInitializer = linkedSetOf() + val nonTrivialVariables = linkedSetOf() for (instruction in pseudocode.instructions) { if (instruction is VariableDeclarationInstruction) { val variableDeclarationElement = instruction.variableDeclarationElement - val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, variableDeclarationElement) - variableDescriptorForDeclaration(descriptor)?.let { - declaredVariables.add(it) + val descriptor = + variableDescriptorForDeclaration( + bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, variableDeclarationElement) + ) ?: continue + + if (isValWithTrivialInitializer(variableDeclarationElement, descriptor)) { + valsWithTrivialInitializer.add(descriptor) + } + else { + nonTrivialVariables.add(descriptor) } } } - return Collections.unmodifiableSet(declaredVariables) + + return VariablesForDeclaration(valsWithTrivialInitializer, nonTrivialVariables) + } + + private fun isValWithTrivialInitializer(variableDeclarationElement: KtDeclaration, descriptor: VariableDescriptor) = + variableDeclarationElement is KtParameter || variableDeclarationElement is KtObjectDeclaration || + variableDeclarationElement.safeAs()?.isVariableWithTrivialInitializer(descriptor) == true + + private fun KtVariableDeclaration.isVariableWithTrivialInitializer(descriptor: VariableDescriptor): Boolean { + if (descriptor.isPropertyWithoutBackingField()) return true + if (isVar) return false + return initializer != null || safeAs()?.delegate != null || this is KtDestructuringDeclarationEntry + } + + private fun VariableDescriptor.isPropertyWithoutBackingField(): Boolean { + if (this !is PropertyDescriptor) return false + return bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, this) != true } // variable initializers - private fun computeVariableInitializers(): Map> { + private fun computeVariableInitializers(): Map> { val blockScopeVariableInfo = pseudocodeVariableDataCollector.blockScopeVariableInfo + val resultForValsWithTrivialInitializer = computeInitInfoForTrivialVals() + + if (rootVariables.nonTrivialVariables.isEmpty()) return resultForValsWithTrivialInitializer + return pseudocodeVariableDataCollector.collectData(TraversalOrder.FORWARD, InitControlFlowInfo()) { instruction: Instruction, incomingEdgesData: Collection -> @@ -94,6 +151,88 @@ class PseudocodeVariablesData(val pseudocode: Pseudocode, private val bindingCon val exitInstructionData = addVariableInitStateFromCurrentInstructionIfAny( instruction, enterInstructionData, blockScopeVariableInfo) Edges(enterInstructionData, exitInstructionData) + }.mapValues { + (instruction, edges) -> + val trivialEdges = resultForValsWithTrivialInitializer[instruction]!! + Edges(trivialEdges.incoming.replaceDelegate(edges.incoming), trivialEdges.outgoing.replaceDelegate(edges.outgoing)) + } + } + + private fun computeInitInfoForTrivialVals(): Map> { + val result = hashMapOf>() + var declaredSet = ImmutableHashSet.empty() + var initSet = ImmutableHashSet.empty() + pseudocode.traverse(TraversalOrder.FORWARD) { instruction -> + val enterState = ReadOnlyInitControlFlowInfoImpl(declaredSet, initSet, null) + when (instruction) { + is VariableDeclarationInstruction -> + extractValWithTrivialInitializer(instruction)?.let { + variableDescriptor -> + declaredSet = declaredSet.add(variableDescriptor) + } + is WriteValueInstruction -> { + val variableDescriptor = extractValWithTrivialInitializer(instruction) + if (variableDescriptor != null && instruction.isTrivialInitializer()) { + initSet = initSet.add(variableDescriptor) + } + } + } + + val afterState = ReadOnlyInitControlFlowInfoImpl(declaredSet, initSet, null) + + result[instruction] = Edges(enterState, afterState) + } + return result + } + + private fun WriteValueInstruction.isTrivialInitializer() = + element is KtVariableDeclaration || element is KtParameter + + private inner class ReadOnlyInitControlFlowInfoImpl( + val declaredSet: ImmutableSet, + val initSet: ImmutableSet, + private val delegate: ReadOnlyInitControlFlowInfo? + ) : ReadOnlyInitControlFlowInfo { + override fun getOrNull(variableDescriptor: VariableDescriptor): VariableControlFlowState? { + if (variableDescriptor in declaredSet) { + return VariableControlFlowState.create(isInitialized = variableDescriptor in initSet, isDeclared = true) + } + return delegate?.getOrNull(variableDescriptor) + } + + override fun checkDefiniteInitializationInWhen(merge: ReadOnlyInitControlFlowInfo): Boolean = + delegate?.checkDefiniteInitializationInWhen(merge) ?: false + + fun replaceDelegate(newDelegate: ReadOnlyInitControlFlowInfo): ReadOnlyInitControlFlowInfo = + ReadOnlyInitControlFlowInfoImpl(declaredSet, initSet, newDelegate) + + override fun asMap(): ImmutableMap { + val initial = delegate?.asMap() ?: ImmutableHashMap.empty() + + return declaredSet.fold(initial) { + acc, variableDescriptor -> + acc.put(variableDescriptor, getOrNull(variableDescriptor)!!) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ReadOnlyInitControlFlowInfoImpl + + if (declaredSet != other.declaredSet) return false + if (initSet != other.initSet) return false + if (delegate != other.delegate) return false + + return true + } + + override fun hashCode(): Int { + var result = declaredSet.hashCode() + result = 31 * result + initSet.hashCode() + result = 31 * result + (delegate?.hashCode() ?: 0) + return result } } @@ -115,7 +254,10 @@ class PseudocodeVariablesData(val pseudocode: Pseudocode, private val bindingCon if (instruction !is WriteValueInstruction && instruction !is VariableDeclarationInstruction) { return enterInstructionData } - val variable = PseudocodeUtil.extractVariableDescriptorIfAny(instruction, bindingContext) ?: return enterInstructionData + val variable = + PseudocodeUtil.extractVariableDescriptorIfAny(instruction, bindingContext) + ?.takeIf { it in rootVariables.nonTrivialVariables } + ?: return enterInstructionData var exitInstructionData = enterInstructionData if (instruction is WriteValueInstruction) { // if writing to already initialized object @@ -129,10 +271,10 @@ class PseudocodeVariablesData(val pseudocode: Pseudocode, private val bindingCon } else { // instruction instanceof VariableDeclarationInstruction - var enterInitState: VariableControlFlowState? = enterInstructionData.getOrNull(variable) - if (enterInitState == null) { - enterInitState = getDefaultValueForInitializers(variable, instruction, blockScopeVariableInfo) - } + val enterInitState = + enterInstructionData.getOrNull(variable) + ?: getDefaultValueForInitializers(variable, instruction, blockScopeVariableInfo) + if (!enterInitState.mayBeInitialized() || !enterInitState.isDeclared) { val variableDeclarationInfo = VariableControlFlowState.create(enterInitState.initState, isDeclared = true) exitInstructionData = exitInstructionData.put(variable, variableDeclarationInfo, enterInitState) @@ -144,45 +286,127 @@ class PseudocodeVariablesData(val pseudocode: Pseudocode, private val bindingCon // variable use val variableUseStatusData: Map> - get() = pseudocodeVariableDataCollector.collectData(TraversalOrder.BACKWARD, UseControlFlowInfo()) { - instruction: Instruction, incomingEdgesData: Collection -> + get() { + val resultForTrivialVals = computeUseInfoForTrivialVals() + if (rootVariables.nonTrivialVariables.isEmpty()) return resultForTrivialVals - val enterResult: UseControlFlowInfo = if (incomingEdgesData.size == 1) { - incomingEdgesData.single() - } - else { - incomingEdgesData.fold(UseControlFlowInfo()) { result, edgeData -> - edgeData.iterator().fold(result) { subResult, (variableDescriptor, variableUseState) -> - subResult.put(variableDescriptor, variableUseState.merge(subResult.getOrNull(variableDescriptor))) + return pseudocodeVariableDataCollector.collectData(TraversalOrder.BACKWARD, UseControlFlowInfo()) { instruction: Instruction, incomingEdgesData: Collection -> + + val enterResult: UseControlFlowInfo = if (incomingEdgesData.size == 1) { + incomingEdgesData.single() + } + else { + incomingEdgesData.fold(UseControlFlowInfo()) { result, edgeData -> + edgeData.iterator().fold(result) { subResult, (variableDescriptor, variableUseState) -> + subResult.put(variableDescriptor, variableUseState.merge(subResult.getOrNull(variableDescriptor))) + } } } - } - val variableDescriptor = PseudocodeUtil.extractVariableDescriptorFromReference(instruction, bindingContext) - if (variableDescriptor == null || instruction !is ReadValueInstruction && instruction !is WriteValueInstruction) { - Edges(enterResult, enterResult) - } - else { - val exitResult = - if (instruction is ReadValueInstruction) { - enterResult.put(variableDescriptor, VariableUseState.READ) - } - else { - var variableUseState: VariableUseState? = enterResult.getOrNull(variableDescriptor) - if (variableUseState == null) { - variableUseState = VariableUseState.UNUSED - } - when (variableUseState) { - VariableUseState.UNUSED, VariableUseState.ONLY_WRITTEN_NEVER_READ -> - enterResult.put(variableDescriptor, VariableUseState.ONLY_WRITTEN_NEVER_READ) - VariableUseState.WRITTEN_AFTER_READ, VariableUseState.READ -> - enterResult.put(variableDescriptor, VariableUseState.WRITTEN_AFTER_READ) - } - } - Edges(enterResult, exitResult) + val variableDescriptor = + PseudocodeUtil.extractVariableDescriptorFromReference(instruction, bindingContext) + ?.takeIf { it in rootVariables.nonTrivialVariables } + if (variableDescriptor == null || instruction !is ReadValueInstruction && instruction !is WriteValueInstruction) { + Edges(enterResult, enterResult) + } + else { + val exitResult = + if (instruction is ReadValueInstruction) { + enterResult.put(variableDescriptor, VariableUseState.READ) + } + else { + var variableUseState: VariableUseState? = enterResult.getOrNull(variableDescriptor) + if (variableUseState == null) { + variableUseState = VariableUseState.UNUSED + } + when (variableUseState) { + VariableUseState.UNUSED, VariableUseState.ONLY_WRITTEN_NEVER_READ -> + enterResult.put(variableDescriptor, VariableUseState.ONLY_WRITTEN_NEVER_READ) + VariableUseState.WRITTEN_AFTER_READ, VariableUseState.READ -> + enterResult.put(variableDescriptor, VariableUseState.WRITTEN_AFTER_READ) + } + } + Edges(enterResult, exitResult) + } + }.mapValues { + (instruction, edges) -> + val edgeForTrivialVals = resultForTrivialVals[instruction]!! + + Edges( + edgeForTrivialVals.incoming.replaceDelegate(edges.incoming), + edgeForTrivialVals.outgoing.replaceDelegate(edges.outgoing) + ) } } + private fun computeUseInfoForTrivialVals(): Map> { + val used = hashSetOf() + + pseudocode.traverse(TraversalOrder.FORWARD) { instruction -> + if (instruction is ReadValueInstruction) { + extractValWithTrivialInitializer(instruction)?.let { + used.add(it) + } + } + } + + val constantUseInfo = ReadOnlyUseControlFlowInfoImpl(used, null) + val constantEdges = Edges(constantUseInfo, constantUseInfo) + val result = hashMapOf>() + + pseudocode.traverse(TraversalOrder.FORWARD) { instruction -> + result[instruction] = constantEdges + } + return result + } + + private fun extractValWithTrivialInitializer(instruction: Instruction): VariableDescriptor? { + return PseudocodeUtil.extractVariableDescriptorIfAny(instruction, bindingContext)?.takeIf { + it in rootVariables.valsWithTrivialInitializer + } + } + + private inner class ReadOnlyUseControlFlowInfoImpl( + val used: Set, + val delegate: ReadOnlyUseControlFlowInfo? + ) : ReadOnlyUseControlFlowInfo { + override fun getOrNull(variableDescriptor: VariableDescriptor): VariableUseState? { + if (variableDescriptor in used) return VariableUseState.READ + return delegate?.getOrNull(variableDescriptor) + } + + fun replaceDelegate(newDelegate: ReadOnlyUseControlFlowInfo): ReadOnlyUseControlFlowInfo = + ReadOnlyUseControlFlowInfoImpl(used, newDelegate) + + override fun asMap(): ImmutableMap { + val initial = delegate?.asMap() ?: ImmutableHashMap.empty() + + return used.fold(initial) { + acc, variableDescriptor -> + acc.put(variableDescriptor, getOrNull(variableDescriptor)!!) + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ReadOnlyUseControlFlowInfoImpl + + if (used != other.used) return false + if (delegate != other.delegate) return false + + return true + } + + override fun hashCode(): Int { + var result = used.hashCode() + result = 31 * result + (delegate?.hashCode() ?: 0) + return result + } + + } + companion object { @JvmStatic diff --git a/compiler/testData/diagnostics/tests/DefaultValuesCheckWithoutBody.kt b/compiler/testData/diagnostics/tests/DefaultValuesCheckWithoutBody.kt index ac90467340b..217b1790e5d 100644 --- a/compiler/testData/diagnostics/tests/DefaultValuesCheckWithoutBody.kt +++ b/compiler/testData/diagnostics/tests/DefaultValuesCheckWithoutBody.kt @@ -6,4 +6,4 @@ abstract class Abst { abstract fun foo(x: Int = y, y: Int = x) } -fun extraDiagnostics(x: Int = y, y: Int) \ No newline at end of file +fun extraDiagnostics(x: Int = y, y: Int) diff --git a/compiler/testData/diagnostics/tests/DefaultValuesTypechecking.kt b/compiler/testData/diagnostics/tests/DefaultValuesTypechecking.kt index a6be46b2654..2ab8a0dbbbf 100644 --- a/compiler/testData/diagnostics/tests/DefaultValuesTypechecking.kt +++ b/compiler/testData/diagnostics/tests/DefaultValuesTypechecking.kt @@ -6,10 +6,10 @@ fun bar(x : Int = "", y : Int = x, z // KT-371 Resolve default parameters for constructors -class A(x : Int = y, y : Int = x) { // None of the references is resolved, no types checked - fun foo(bool: Boolean, a: Int = b, b: String = a) {} +class A(x : Int = y, y : Int = x) { // None of the references is resolved, no types checked + fun foo(bool: Boolean, a: Int = b, b: String = a) {} } val z = 3 -fun foo(x: Int = y, y: Int = x, i : Int = z): Int = x + y \ No newline at end of file +fun foo(x: Int = y, y: Int = x, i : Int = z): Int = x + y diff --git a/compiler/testData/diagnostics/tests/LValueAssignment.kt b/compiler/testData/diagnostics/tests/LValueAssignment.kt index 20f4baeea06..b98be9fd2e9 100644 --- a/compiler/testData/diagnostics/tests/LValueAssignment.kt +++ b/compiler/testData/diagnostics/tests/LValueAssignment.kt @@ -51,7 +51,7 @@ fun cannotBe() { 5 = 34 } -fun canBe(i0: Int, j: Int) { +fun canBe(i0: Int, j: Int) { var i = i0 (label@ i) = 34 @@ -61,7 +61,7 @@ fun canBe(i0: Int, j: Int) { (l@ a.a) = 3894 } -fun canBe2(j: Int) { +fun canBe2(j: Int) { (label@ j) = 34 } @@ -140,4 +140,4 @@ fun Array.checkThis() { abstract class Ab { abstract fun getArray() : Array -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt index 996e2b017c6..a0c29641d42 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/UninitializedOrReassignedVariables.kt @@ -53,7 +53,7 @@ fun t2() { class A() {} -fun t4(a: A) { +fun t4(a: A) { a = A() } @@ -61,7 +61,7 @@ fun t4(a: A) { // reassigned vals fun t1() { - val a : Int = 1 + val a : Int = 1 a = 2 var b : Int = 1 @@ -83,7 +83,7 @@ enum class ProtocolState { fun t3() { val x: ProtocolState = ProtocolState.WAITING x = x.signal() - x = x.signal() //repeat for x + x = x.signal() //repeat for x } fun t4() { @@ -187,7 +187,7 @@ class AnonymousInitializers(var a: String, val b: String) { } } -fun reassignFunParams(a: Int) { +fun reassignFunParams(a: Int) { a = 1 } @@ -345,4 +345,4 @@ fun test(m : M) { fun test1(m : M) { m.x++ m.y-- -} \ No newline at end of file +} diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/doWhileNotDefined.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/doWhileNotDefined.kt index e41bcd1a76e..92c4f436540 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/doWhileNotDefined.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/doWhileNotDefined.kt @@ -2,5 +2,5 @@ fun test(cond1: Boolean) { do { if (cond1) continue val cond2 = false - } while (cond2) // cond2 may be not defined here + } while (cond2) // cond2 may be not defined here } diff --git a/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt897.kt b/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt897.kt index eb279e36d50..ee10f766706 100644 --- a/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt897.kt +++ b/compiler/testData/diagnostics/tests/controlFlowAnalysis/kt897.kt @@ -4,7 +4,7 @@ package kt897 class A() { init { - i = 11 + i = 11 } val i : Int? = null // must be an error diff --git a/compiler/testData/diagnostics/tests/declarationChecks/DataFlowInMultiDeclInFor.kt b/compiler/testData/diagnostics/tests/declarationChecks/DataFlowInMultiDeclInFor.kt index 1feb317b1f0..0911d17433c 100644 --- a/compiler/testData/diagnostics/tests/declarationChecks/DataFlowInMultiDeclInFor.kt +++ b/compiler/testData/diagnostics/tests/declarationChecks/DataFlowInMultiDeclInFor.kt @@ -8,7 +8,7 @@ class A { } fun foo(list: List) { - for (var (c1, c2, c3) in list) { + for (var (c1, c2, c3) in list) { c1 = 1 c3 + 1 }