From bce62430ce2af6d8fdd95c1df1f5bd359c241346 Mon Sep 17 00:00:00 2001 From: Dmitriy Novozhilov Date: Tue, 14 Sep 2021 09:55:51 +0300 Subject: [PATCH] [FIR] Split CfaUtils to multiple files --- .../analysis/cfa/DeclaredVariableCollector.kt | 86 ++++++++ .../LocalPropertyAndCapturedWriteCollector.kt | 68 +++++++ .../cfa/PropertyInitializationInfo.kt | 69 +++++++ ...=> PropertyInitializationInfoCollector.kt} | 191 +----------------- .../test/runners/AbstractDiagnosticTest.kt | 1 + 5 files changed, 227 insertions(+), 188 deletions(-) create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/DeclaredVariableCollector.kt create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/LocalPropertyAndCapturedWriteCollector.kt create mode 100644 compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/PropertyInitializationInfo.kt rename compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/{CfaUtils.kt => PropertyInitializationInfoCollector.kt} (51%) diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/DeclaredVariableCollector.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/DeclaredVariableCollector.kt new file mode 100644 index 00000000000..89f2c6eb2dd --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/DeclaredVariableCollector.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.cfa + +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.expressions.FirDoWhileLoop +import org.jetbrains.kotlin.fir.expressions.FirLoop +import org.jetbrains.kotlin.fir.expressions.FirStatement +import org.jetbrains.kotlin.fir.expressions.FirWhileLoop +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol +import org.jetbrains.kotlin.fir.util.SetMultimap +import org.jetbrains.kotlin.fir.util.setMultimapOf +import org.jetbrains.kotlin.fir.visitors.FirVisitor + +// Note that [PreliminaryLoopVisitor] in FIR DFA collects assigned variable names. +// This one collects declared variable symbols per capturing statements. +class DeclaredVariableCollector { + val declaredVariablesPerElement: SetMultimap = setMultimapOf() + + fun enterCapturingStatement(statement: FirStatement): Set { + assert(statement is FirLoop || statement is FirClass || statement is FirFunction) + if (statement !in declaredVariablesPerElement) { + statement.accept(visitor, null) + } + return declaredVariablesPerElement[statement] + } + + fun exitCapturingStatement(statement: FirStatement) { + assert(statement is FirLoop || statement is FirClass || statement is FirFunction) + declaredVariablesPerElement.removeKey(statement) + } + + fun resetState() { + declaredVariablesPerElement.clear() + } + + // FirStatement -- closest statement (loop/lambda/local declaration) which may contain reassignments + private val visitor = object : FirVisitor() { + override fun visitElement(element: FirElement, data: FirStatement?) { + element.acceptChildren(this, data) + } + + override fun visitProperty(property: FirProperty, data: FirStatement?) { + if (property.isLocal) { + requireNotNull(data) + declaredVariablesPerElement.put(data, property.symbol) + } + visitElement(property, data) + } + + override fun visitWhileLoop(whileLoop: FirWhileLoop, data: FirStatement?) { + visitCapturingStatement(whileLoop, data) + } + + override fun visitDoWhileLoop(doWhileLoop: FirDoWhileLoop, data: FirStatement?) { + visitCapturingStatement(doWhileLoop, data) + } + + override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: FirStatement?) { + visitCapturingStatement(anonymousFunction, data) + } + + override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: FirStatement?) { + visitCapturingStatement(simpleFunction, data) + } + + override fun visitRegularClass(regularClass: FirRegularClass, data: FirStatement?) { + visitCapturingStatement(regularClass, data) + } + + override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: FirStatement?) { + visitCapturingStatement(anonymousObject, data) + } + + private fun visitCapturingStatement(statement: FirStatement, parent: FirStatement?) { + visitElement(statement, statement) + if (parent != null) { + declaredVariablesPerElement.putAll(parent, declaredVariablesPerElement[statement]) + } + } + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/LocalPropertyAndCapturedWriteCollector.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/LocalPropertyAndCapturedWriteCollector.kt new file mode 100644 index 00000000000..40e5fc8f231 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/LocalPropertyAndCapturedWriteCollector.kt @@ -0,0 +1,68 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.cfa + +import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.utils.referredPropertySymbol +import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol + +class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() { + companion object { + fun collect(graph: ControlFlowGraph): Pair, Set> { + val collector = LocalPropertyAndCapturedWriteCollector() + graph.traverse(TraverseDirection.Forward, collector) + return collector.symbols.keys to collector.capturedWrites + } + } + + // Mapping from a property symbol to its declaration context + // `true` if the (local) property is declared in the currently visited function. + // `false` if it is declared in a lambda or a local function (inside the currently visited function). + private val symbols: MutableMap = mutableMapOf() + + private val lambdaOrLocalFunctionStack: MutableList = mutableListOf() + private val capturedWrites: MutableSet = mutableSetOf() + + override fun visitNode(node: CFGNode<*>) {} + + override fun visitVariableDeclarationNode(node: VariableDeclarationNode) { + symbols[node.fir.symbol] = lambdaOrLocalFunctionStack.lastOrNull() == null + } + + override fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode) { + lambdaOrLocalFunctionStack.add(node.fir) + } + + override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) { + lambdaOrLocalFunctionStack.remove(node.fir.anonymousFunction) + } + + override fun visitLocalFunctionDeclarationNode(node: LocalFunctionDeclarationNode, data: Nothing?) { + lambdaOrLocalFunctionStack.add(node.fir) + } + + override fun visitFunctionExitNode(node: FunctionExitNode) { + lambdaOrLocalFunctionStack.remove(node.fir) + } + + override fun visitVariableAssignmentNode(node: VariableAssignmentNode) { + // Check if this variable assignment is inside a lambda or a local function. + if (lambdaOrLocalFunctionStack.isEmpty()) return + + // Check if the assigned variable doesn't belong to any lambda or local function. + val symbol = node.fir.referredPropertySymbol ?: return + if (symbol !in symbols || symbols[symbol] == false) return + + // If all nested declarations are lambdas that are invoked in-place (according to the contract), + // this variable assignment is not a captured write. + if (lambdaOrLocalFunctionStack.all { it is FirAnonymousFunction && it.invocationKind.isInPlace }) return + + capturedWrites.add(node.fir) + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/PropertyInitializationInfo.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/PropertyInitializationInfo.kt new file mode 100644 index 00000000000..23a63dc2650 --- /dev/null +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/PropertyInitializationInfo.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.fir.analysis.cfa + +import kotlinx.collections.immutable.PersistentMap +import kotlinx.collections.immutable.persistentMapOf +import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.EdgeLabel +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.NormalPath +import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol + +abstract class EventOccurrencesRangeInfo, K : Any>( + map: PersistentMap = persistentMapOf() +) : ControlFlowInfo(map) { + + override fun merge(other: E): E = + operation(other, EventOccurrencesRange::or) + + fun plus(other: E): E = + when { + isEmpty() -> other + other.isEmpty() -> + @Suppress("UNCHECKED_CAST") + this as E + else -> operation(other, EventOccurrencesRange::plus) + } + + private inline fun operation(other: E, op: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange): E { + @Suppress("UNCHECKED_CAST") + var result = this as E + for (symbol in keys.union(other.keys)) { + val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO + val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO + result = result.put(symbol, op.invoke(kind1, kind2)) + } + return result + } +} + +class PropertyInitializationInfo( + map: PersistentMap = persistentMapOf() +) : EventOccurrencesRangeInfo(map) { + companion object { + val EMPTY = PropertyInitializationInfo() + } + + override val constructor: (PersistentMap) -> PropertyInitializationInfo = + ::PropertyInitializationInfo + + override val empty: () -> PropertyInitializationInfo = + ::EMPTY +} + +class PathAwarePropertyInitializationInfo( + map: PersistentMap = persistentMapOf() +) : PathAwareControlFlowInfo(map) { + companion object { + val EMPTY = PathAwarePropertyInitializationInfo(persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY)) + } + + override val constructor: (PersistentMap) -> PathAwarePropertyInitializationInfo = + ::PathAwarePropertyInitializationInfo + + override val empty: () -> PathAwarePropertyInitializationInfo = + ::EMPTY +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/CfaUtils.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/PropertyInitializationInfoCollector.kt similarity index 51% rename from compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/CfaUtils.kt rename to compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/PropertyInitializationInfoCollector.kt index 2cc0ba446b6..4e9506391fb 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/CfaUtils.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/cfa/PropertyInitializationInfoCollector.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @@ -8,126 +8,10 @@ package org.jetbrains.kotlin.fir.analysis.cfa import kotlinx.collections.immutable.PersistentMap import kotlinx.collections.immutable.persistentMapOf import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange -import org.jetbrains.kotlin.fir.FirElement -import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.utils.referredPropertySymbol -import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol -import org.jetbrains.kotlin.fir.util.SetMultimap -import org.jetbrains.kotlin.fir.util.setMultimapOf -import org.jetbrains.kotlin.fir.visitors.FirVisitor - -abstract class EventOccurrencesRangeInfo, K : Any>( - map: PersistentMap = persistentMapOf() -) : ControlFlowInfo(map) { - - override fun merge(other: E): E = - operation(other, EventOccurrencesRange::or) - - fun plus(other: E): E = - when { - isEmpty() -> other - other.isEmpty() -> - @Suppress("UNCHECKED_CAST") - this as E - else -> operation(other, EventOccurrencesRange::plus) - } - - private inline fun operation(other: E, op: (EventOccurrencesRange, EventOccurrencesRange) -> EventOccurrencesRange): E { - @Suppress("UNCHECKED_CAST") - var result = this as E - for (symbol in keys.union(other.keys)) { - val kind1 = this[symbol] ?: EventOccurrencesRange.ZERO - val kind2 = other[symbol] ?: EventOccurrencesRange.ZERO - result = result.put(symbol, op.invoke(kind1, kind2)) - } - return result - } -} - -class PropertyInitializationInfo( - map: PersistentMap = persistentMapOf() -) : EventOccurrencesRangeInfo(map) { - companion object { - val EMPTY = PropertyInitializationInfo() - } - - override val constructor: (PersistentMap) -> PropertyInitializationInfo = - ::PropertyInitializationInfo - - override val empty: () -> PropertyInitializationInfo = - ::EMPTY -} - -class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() { - companion object { - fun collect(graph: ControlFlowGraph): Pair, Set> { - val collector = LocalPropertyAndCapturedWriteCollector() - graph.traverse(TraverseDirection.Forward, collector) - return collector.symbols.keys to collector.capturedWrites - } - } - - // Mapping from a property symbol to its declaration context - // `true` if the (local) property is declared in the currently visited function. - // `false` if it is declared in a lambda or a local function (inside the currently visited function). - private val symbols: MutableMap = mutableMapOf() - - private val lambdaOrLocalFunctionStack: MutableList = mutableListOf() - private val capturedWrites: MutableSet = mutableSetOf() - - override fun visitNode(node: CFGNode<*>) {} - - override fun visitVariableDeclarationNode(node: VariableDeclarationNode) { - symbols[node.fir.symbol] = lambdaOrLocalFunctionStack.lastOrNull() == null - } - - override fun visitPostponedLambdaEnterNode(node: PostponedLambdaEnterNode) { - lambdaOrLocalFunctionStack.add(node.fir) - } - - override fun visitPostponedLambdaExitNode(node: PostponedLambdaExitNode) { - lambdaOrLocalFunctionStack.remove(node.fir.anonymousFunction) - } - - override fun visitLocalFunctionDeclarationNode(node: LocalFunctionDeclarationNode, data: Nothing?) { - lambdaOrLocalFunctionStack.add(node.fir) - } - - override fun visitFunctionExitNode(node: FunctionExitNode) { - lambdaOrLocalFunctionStack.remove(node.fir) - } - - override fun visitVariableAssignmentNode(node: VariableAssignmentNode) { - // Check if this variable assignment is inside a lambda or a local function. - if (lambdaOrLocalFunctionStack.isEmpty()) return - - // Check if the assigned variable doesn't belong to any lambda or local function. - val symbol = node.fir.referredPropertySymbol ?: return - if (symbol !in symbols || symbols[symbol] == false) return - - // If all nested declarations are lambdas that are invoked in-place (according to the contract), - // this variable assignment is not a captured write. - if (lambdaOrLocalFunctionStack.all { it is FirAnonymousFunction && it.invocationKind.isInPlace }) return - - capturedWrites.add(node.fir) - } -} - -class PathAwarePropertyInitializationInfo( - map: PersistentMap = persistentMapOf() -) : PathAwareControlFlowInfo(map) { - companion object { - val EMPTY = PathAwarePropertyInitializationInfo(persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY)) - } - - override val constructor: (PersistentMap) -> PathAwarePropertyInitializationInfo = - ::PathAwarePropertyInitializationInfo - - override val empty: () -> PathAwarePropertyInitializationInfo = - ::EMPTY -} class PropertyInitializationInfoCollector( private val localProperties: Set, @@ -260,75 +144,6 @@ class PropertyInitializationInfoCollector( } } -// Note that [PreliminaryLoopVisitor] in FIR DFA collects assigned variable names. -// This one collects declared variable symbols per capturing statements. -class DeclaredVariableCollector { - val declaredVariablesPerElement: SetMultimap = setMultimapOf() - - fun enterCapturingStatement(statement: FirStatement): Set { - assert(statement is FirLoop || statement is FirClass || statement is FirFunction) - if (statement !in declaredVariablesPerElement) { - statement.accept(visitor, null) - } - return declaredVariablesPerElement[statement] - } - - fun exitCapturingStatement(statement: FirStatement) { - assert(statement is FirLoop || statement is FirClass || statement is FirFunction) - declaredVariablesPerElement.removeKey(statement) - } - - fun resetState() { - declaredVariablesPerElement.clear() - } - - // FirStatement -- closest statement (loop/lambda/local declaration) which may contain reassignments - private val visitor = object : FirVisitor() { - override fun visitElement(element: FirElement, data: FirStatement?) { - element.acceptChildren(this, data) - } - - override fun visitProperty(property: FirProperty, data: FirStatement?) { - if (property.isLocal) { - requireNotNull(data) - declaredVariablesPerElement.put(data, property.symbol) - } - visitElement(property, data) - } - - override fun visitWhileLoop(whileLoop: FirWhileLoop, data: FirStatement?) { - visitCapturingStatement(whileLoop, data) - } - - override fun visitDoWhileLoop(doWhileLoop: FirDoWhileLoop, data: FirStatement?) { - visitCapturingStatement(doWhileLoop, data) - } - - override fun visitAnonymousFunction(anonymousFunction: FirAnonymousFunction, data: FirStatement?) { - visitCapturingStatement(anonymousFunction, data) - } - - override fun visitSimpleFunction(simpleFunction: FirSimpleFunction, data: FirStatement?) { - visitCapturingStatement(simpleFunction, data) - } - - override fun visitRegularClass(regularClass: FirRegularClass, data: FirStatement?) { - visitCapturingStatement(regularClass, data) - } - - override fun visitAnonymousObject(anonymousObject: FirAnonymousObject, data: FirStatement?) { - visitCapturingStatement(anonymousObject, data) - } - - private fun visitCapturingStatement(statement: FirStatement, parent: FirStatement?) { - visitElement(statement, statement) - if (parent != null) { - declaredVariablesPerElement.putAll(parent, declaredVariablesPerElement[statement]) - } - } - } -} - internal fun

, S : ControlFlowInfo, K : Any> addRange( pathAwareInfo: P, key: K, @@ -341,7 +156,7 @@ internal fun

, S : ControlFlowInfo existingKind + range }, constructor) } -internal fun

, S : ControlFlowInfo, K : Any> overwriteRange( +private fun

, S : ControlFlowInfo, K : Any> overwriteRange( pathAwareInfo: P, key: K, range: EventOccurrencesRange, diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractDiagnosticTest.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractDiagnosticTest.kt index 732f91f4752..461696a1435 100644 --- a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractDiagnosticTest.kt +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/runners/AbstractDiagnosticTest.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.test.runners import org.jetbrains.kotlin.config.ExplicitApiMode +import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.platform.jvm.JvmPlatforms import org.jetbrains.kotlin.test.TestJdkKind import org.jetbrains.kotlin.test.builders.TestConfigurationBuilder