[FIR] Split CfaUtils to multiple files

This commit is contained in:
Dmitriy Novozhilov
2021-09-14 09:55:51 +03:00
committed by teamcityserver
parent bdb61ec5d7
commit bce62430ce
5 changed files with 227 additions and 188 deletions
@@ -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<FirStatement, FirPropertySymbol> = setMultimapOf()
fun enterCapturingStatement(statement: FirStatement): Set<FirPropertySymbol> {
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<Unit, FirStatement?>() {
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])
}
}
}
}
@@ -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<FirPropertySymbol>, Set<FirVariableAssignment>> {
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<FirPropertySymbol, Boolean> = mutableMapOf()
private val lambdaOrLocalFunctionStack: MutableList<FirFunction> = mutableListOf()
private val capturedWrites: MutableSet<FirVariableAssignment> = 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)
}
}
@@ -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<E : EventOccurrencesRangeInfo<E, K>, K : Any>(
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
) : ControlFlowInfo<E, K, EventOccurrencesRange>(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<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
) : EventOccurrencesRangeInfo<PropertyInitializationInfo, FirPropertySymbol>(map) {
companion object {
val EMPTY = PropertyInitializationInfo()
}
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
::PropertyInitializationInfo
override val empty: () -> PropertyInitializationInfo =
::EMPTY
}
class PathAwarePropertyInitializationInfo(
map: PersistentMap<EdgeLabel, PropertyInitializationInfo> = persistentMapOf()
) : PathAwareControlFlowInfo<PathAwarePropertyInitializationInfo, PropertyInitializationInfo>(map) {
companion object {
val EMPTY = PathAwarePropertyInitializationInfo(persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY))
}
override val constructor: (PersistentMap<EdgeLabel, PropertyInitializationInfo>) -> PathAwarePropertyInitializationInfo =
::PathAwarePropertyInitializationInfo
override val empty: () -> PathAwarePropertyInitializationInfo =
::EMPTY
}
@@ -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<E : EventOccurrencesRangeInfo<E, K>, K : Any>(
map: PersistentMap<K, EventOccurrencesRange> = persistentMapOf()
) : ControlFlowInfo<E, K, EventOccurrencesRange>(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<FirPropertySymbol, EventOccurrencesRange> = persistentMapOf()
) : EventOccurrencesRangeInfo<PropertyInitializationInfo, FirPropertySymbol>(map) {
companion object {
val EMPTY = PropertyInitializationInfo()
}
override val constructor: (PersistentMap<FirPropertySymbol, EventOccurrencesRange>) -> PropertyInitializationInfo =
::PropertyInitializationInfo
override val empty: () -> PropertyInitializationInfo =
::EMPTY
}
class LocalPropertyAndCapturedWriteCollector private constructor() : ControlFlowGraphVisitorVoid() {
companion object {
fun collect(graph: ControlFlowGraph): Pair<Set<FirPropertySymbol>, Set<FirVariableAssignment>> {
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<FirPropertySymbol, Boolean> = mutableMapOf()
private val lambdaOrLocalFunctionStack: MutableList<FirFunction> = mutableListOf()
private val capturedWrites: MutableSet<FirVariableAssignment> = 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<EdgeLabel, PropertyInitializationInfo> = persistentMapOf()
) : PathAwareControlFlowInfo<PathAwarePropertyInitializationInfo, PropertyInitializationInfo>(map) {
companion object {
val EMPTY = PathAwarePropertyInitializationInfo(persistentMapOf(NormalPath to PropertyInitializationInfo.EMPTY))
}
override val constructor: (PersistentMap<EdgeLabel, PropertyInitializationInfo>) -> PathAwarePropertyInitializationInfo =
::PathAwarePropertyInitializationInfo
override val empty: () -> PathAwarePropertyInitializationInfo =
::EMPTY
}
class PropertyInitializationInfoCollector(
private val localProperties: Set<FirPropertySymbol>,
@@ -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<FirStatement, FirPropertySymbol> = setMultimapOf()
fun enterCapturingStatement(statement: FirStatement): Set<FirPropertySymbol> {
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<Unit, FirStatement?>() {
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 <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> addRange(
pathAwareInfo: P,
key: K,
@@ -341,7 +156,7 @@ internal fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, Even
return updateRange(pathAwareInfo, key, { existingKind -> existingKind + range }, constructor)
}
internal fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> overwriteRange(
private fun <P : PathAwareControlFlowInfo<P, S>, S : ControlFlowInfo<S, K, EventOccurrencesRange>, K : Any> overwriteRange(
pathAwareInfo: P,
key: K,
range: EventOccurrencesRange,
@@ -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