From 402ea98e0237d5c794a3b3fc60b6ec24cb0184ee Mon Sep 17 00:00:00 2001 From: pyos Date: Thu, 24 Nov 2022 13:50:01 +0100 Subject: [PATCH] FIR DFA: align FirLocalVariableAssignmentAnalyzer with the graph builder I.e. maintain a set of seen lambdas where each marked as either "data flow only" or "both data and control flow". The latter are truly parallel with the function being analyzed, while the former have technically already terminated, we just don't know the types inside them because they may not have been analyzed yet. --- .../fir/resolve/dfa/FirDataFlowAnalyzer.kt | 138 ++++------- .../dfa/FirLocalVariableAssignmentAnalyzer.kt | 217 ++++++++---------- .../dfa/cfg/ControlFlowGraphBuilder.kt | 4 +- ...bstractBodyResolveTransformerDispatcher.kt | 2 +- .../smartcasts/lambdaInCallArgs.fir.kt | 13 +- .../smartcasts/lambdaInCallArgs.kt | 13 +- .../smartcasts/lambdaInCallArgs.txt | 1 + 7 files changed, 168 insertions(+), 220 deletions(-) diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt index e3082f20d0b..fa2b4cc4cf9 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirDataFlowAnalyzer.kt @@ -35,19 +35,16 @@ import org.jetbrains.kotlin.fir.types.* import org.jetbrains.kotlin.name.StandardClassIds import org.jetbrains.kotlin.types.ConstantValueKind import org.jetbrains.kotlin.util.OperatorNameConventions -import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull -class DataFlowAnalyzerContext( - val graphBuilder: ControlFlowGraphBuilder, - variableStorage: VariableStorageImpl, - val preliminaryLoopVisitor: PreliminaryLoopVisitor, - val variablesClearedBeforeLoop: Stack>, -) { - var variableStorage = variableStorage +class DataFlowAnalyzerContext(session: FirSession) { + val graphBuilder = ControlFlowGraphBuilder() + val preliminaryLoopVisitor = PreliminaryLoopVisitor() + val variablesClearedBeforeLoop = stackOf>() + internal val variableAssignmentAnalyzer = FirLocalVariableAssignmentAnalyzer() + + var variableStorage = VariableStorageImpl(session) private set - internal var firLocalVariableAssignmentAnalyzer: FirLocalVariableAssignmentAnalyzer? = null - private var assignmentCounter = 0 fun newAssignmentIndex(): Int { @@ -56,18 +53,10 @@ class DataFlowAnalyzerContext( fun reset() { graphBuilder.reset() - variableStorage = variableStorage.clear() preliminaryLoopVisitor.resetState() variablesClearedBeforeLoop.reset() - firLocalVariableAssignmentAnalyzer = null - } - - companion object { - fun empty(session: FirSession): DataFlowAnalyzerContext = - DataFlowAnalyzerContext( - ControlFlowGraphBuilder(), VariableStorageImpl(session), - PreliminaryLoopVisitor(), stackOf() - ) + variableAssignmentAnalyzer.reset() + variableStorage = variableStorage.clear() } } @@ -136,11 +125,8 @@ abstract class FirDataFlowAnalyzer( // ----------------------------------- Requests ----------------------------------- - fun isAccessToUnstableLocalVariable(expression: FirExpression): Boolean { - val analyzer = context.firLocalVariableAssignmentAnalyzer ?: return false - val realFir = expression.unwrapElement() as? FirQualifiedAccessExpression ?: return false - return analyzer.isAccessToUnstableLocalVariable(realFir) - } + fun isAccessToUnstableLocalVariable(expression: FirExpression): Boolean = + context.variableAssignmentAnalyzer.isAccessToUnstableLocalVariable(expression) open fun getTypeUsingSmartcastInfo(expression: FirExpression): Pair>? { val flow = graphBuilder.lastNode.flow @@ -164,25 +150,38 @@ abstract class FirDataFlowAnalyzer( fun enterFunction(function: FirFunction) { if (function is FirDefaultPropertyAccessor) return - if (function is FirAnonymousFunction) { - enterAnonymousFunction(function) - return - } - // All non-lambda function are treated as concurrent since we do not make any assumption about when and how it's invoked. - getOrCreateLocalVariableAssignmentAnalyzer(function)?.enterLocalFunction(function) - val (functionEnterNode, localFunctionNode) = graphBuilder.enterFunction(function) + val (localFunctionNode, functionEnterNode) = if (function is FirAnonymousFunction) { + graphBuilder.enterAnonymousFunction(function) + } else { + graphBuilder.enterFunction(function) + } localFunctionNode?.mergeIncomingFlow() - functionEnterNode.mergeIncomingFlow() + functionEnterNode.mergeIncomingFlow { + // TODO: ||? + if (function is FirAnonymousFunction && function.invocationKind?.canBeRevisited() != false) { + enterCapturingStatement(it, function) + } + } + context.variableAssignmentAnalyzer.enterFunction(function) } fun exitFunction(function: FirFunction): FirControlFlowGraphReference? { if (function is FirDefaultPropertyAccessor) return null - if (function is FirAnonymousFunction) { - return exitAnonymousFunction(function) + + context.variableAssignmentAnalyzer.exitFunction() + // TODO: ||? + if (function is FirAnonymousFunction && function.invocationKind?.canBeRevisited() != false) { + exitCapturingStatement(function) + } + + if (function is FirAnonymousFunction) { + val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(function) + functionExitNode.mergeIncomingFlow() + postponedLambdaExitNode?.mergeIncomingFlow() + resetReceivers() // roll back to state before function + return FirControlFlowGraphReferenceImpl(graph) } - // All non-lambda function are treated as concurrent since we do not make any assumption about when and how it's invoked. - getOrCreateLocalVariableAssignmentAnalyzer(function)?.exitLocalFunction(function) val (node, graph) = graphBuilder.exitFunction(function) node.mergeIncomingFlow() @@ -202,35 +201,7 @@ abstract class FirDataFlowAnalyzer( // ----------------------------------- Anonymous function ----------------------------------- - private fun enterAnonymousFunction(anonymousFunction: FirAnonymousFunction) { - getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.apply { - finishPostponedAnonymousFunction() - enterLocalFunction(anonymousFunction) - } - val (functionDeclarationNode, functionEnterNode) = graphBuilder.enterAnonymousFunction(anonymousFunction) - functionDeclarationNode?.mergeIncomingFlow() - functionEnterNode.mergeIncomingFlow { - if (anonymousFunction.invocationKind?.canBeRevisited() != false) { - enterCapturingStatement(it, anonymousFunction) - } - } - } - - private fun exitAnonymousFunction(anonymousFunction: FirAnonymousFunction): FirControlFlowGraphReference { - getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.exitLocalFunction(anonymousFunction) - val (functionExitNode, postponedLambdaExitNode, graph) = graphBuilder.exitAnonymousFunction(anonymousFunction) - if (anonymousFunction.invocationKind?.canBeRevisited() != false) { - exitCapturingStatement(anonymousFunction) - } - functionExitNode.mergeIncomingFlow() - postponedLambdaExitNode?.mergeIncomingFlow() - resetReceivers() // roll back to state before function - return FirControlFlowGraphReferenceImpl(graph) - } - fun visitPostponedAnonymousFunction(anonymousFunctionExpression: FirAnonymousFunctionExpression) { - val anonymousFunction = anonymousFunctionExpression.anonymousFunction - getOrCreateLocalVariableAssignmentAnalyzer(anonymousFunction)?.visitPostponedAnonymousFunction(anonymousFunction) graphBuilder.visitPostponedAnonymousFunction(anonymousFunctionExpression).mergeIncomingFlow() } @@ -809,7 +780,6 @@ abstract class FirDataFlowAnalyzer( graphBuilder.exitResolvedQualifierNode(resolvedQualifier).mergeIncomingFlow() } - private var functionCallLevel = 0 private var resolvingAugmentedAssignmentOptions: Boolean = false // The expected sequence of calls for augmented assignment: @@ -822,10 +792,7 @@ abstract class FirDataFlowAnalyzer( // 7. exitFunctionCall(top-level call in the chosen option) fun enterAugmentedAssignmentCall() { graphBuilder.enterCall() - // Add an extra space in the local variable assignment analyzer. That way all postponed - // lambdas from all alternatives ([get+]plusAssign/[get+]plus[+set]) will be collected - // into that space and only removed when `exitFunctionCall` finalizes the chosen option. - functionCallLevel++ + context.variableAssignmentAnalyzer.enterFunctionCall(emptyList()) } fun enterSelectAugmentedAssignmentCall() { @@ -839,23 +806,16 @@ abstract class FirDataFlowAnalyzer( } fun enterFunctionCall(functionCall: FirFunctionCall) { - val lambdaArgs = functionCall.arguments.mapNotNullTo(mutableSetOf()) { it.unwrapAnonymousFunctionExpression() } - val localVariableAssignmentAnalyzer = context.firLocalVariableAssignmentAnalyzer - ?: if (lambdaArgs.isNotEmpty()) getOrCreateLocalVariableAssignmentAnalyzer(lambdaArgs.first()) else null - localVariableAssignmentAnalyzer?.enterFunctionCall(lambdaArgs, functionCallLevel) - functionCallLevel++ - if (!resolvingAugmentedAssignmentOptions) { - graphBuilder.enterCall() - } + if (resolvingAugmentedAssignmentOptions) return // shouldn't be any lambda arguments anyway, they're visited before that + context.variableAssignmentAnalyzer.enterFunctionCall(functionCall.arguments.mapNotNull { it.unwrapAnonymousFunctionExpression() }) + graphBuilder.enterCall() } fun exitFunctionCall(functionCall: FirFunctionCall, callCompleted: Boolean) { - functionCallLevel-- - context.firLocalVariableAssignmentAnalyzer?.exitFunctionCall(callCompleted) - if (!resolvingAugmentedAssignmentOptions) { - graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow { - processConditionalContract(it, functionCall) - } + if (resolvingAugmentedAssignmentOptions) return + context.variableAssignmentAnalyzer.exitFunctionCall(callCompleted) + graphBuilder.exitFunctionCall(functionCall, callCompleted).mergeIncomingFlow { + processConditionalContract(it, functionCall) } } @@ -1180,16 +1140,6 @@ abstract class FirDataFlowAnalyzer( // ------------------------------------------------------ Utils ------------------------------------------------------ - private fun getOrCreateLocalVariableAssignmentAnalyzer(firFunction: FirFunction): FirLocalVariableAssignmentAnalyzer? { - // Only return analyzer for nested functions so that we won't waste time on functions that don't contain any lambda or local - // function. - val rootFunction = components.containingDeclarations.firstIsInstanceOrNull() ?: return null - if (rootFunction == firFunction) return null - return context.firLocalVariableAssignmentAnalyzer ?: FirLocalVariableAssignmentAnalyzer.analyzeFunction(rootFunction).also { - context.firLocalVariableAssignmentAnalyzer = it - } - } - private val CFGNode<*>.livePreviousFlows: List get() = previousNodes.mapNotNull { it.takeIf { this.isDead || !it.isDead }?.flow } diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt index c667cac7425..c67ff73a02b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/FirLocalVariableAssignmentAnalyzer.kt @@ -5,7 +5,6 @@ package org.jetbrains.kotlin.fir.resolve.dfa -import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange import org.jetbrains.kotlin.contracts.description.isInPlace import org.jetbrains.kotlin.fir.FirElement import org.jetbrains.kotlin.fir.declarations.* @@ -16,7 +15,6 @@ import org.jetbrains.kotlin.fir.references.FirReference import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol import org.jetbrains.kotlin.fir.visitors.FirVisitor import org.jetbrains.kotlin.name.Name -import org.jetbrains.kotlin.utils.addToStdlib.popLast /** * Helper that checks if an access to a local variable access is stable. @@ -25,141 +23,121 @@ import org.jetbrains.kotlin.utils.addToStdlib.popLast * [isAccessToUnstableLocalVariable] only works for an access during the natural FIR tree traversal. This class will not work if one * queries after the traversal is done. **/ -internal class FirLocalVariableAssignmentAnalyzer( - private val assignedLocalVariablesByFunction: Map, FunctionFork> -) { - /** - * Stack storing concurrent lambda arguments for the current visited anonymous function. For example - * ``` - * callWithMultipleLambdaExactlyOnceEach( - * l1 = { x.length }, - * l2 = { x = null } - * ) - * ``` - * From the call, it's nondeterministic whether `l1` runs before `l2` or vice versa. So when handling `l1`, we must mark all variables - * touched in `l2` unstable. - */ - private val concurrentLambdaArgsStack: MutableList> = mutableListOf() +internal class FirLocalVariableAssignmentAnalyzer { + private var rootFunction: FirFunction? = null + private var assignedLocalVariablesByFunction: Map, FunctionFork>? = null - /** - * Stack whose element tracks all concurrently modified variables in execution paths other than this one. It's a stack because after - * exiting a local function, we must restore the variables to the state before entering this local function. Initially, there is an - * empty set for the root function when we starts the analysis. - */ - private val concurrentlyAssignedLocalVariablesStack: MutableList> = mutableListOf(mutableSetOf()) + private val functionScopes: Stack>> = stackOf() - /** - * Temporary storage that tracks concurrently modified variables during function call resolution. For example, consider the following, - * - * ``` - * foo(bar { x= null }, x.length) - * ``` - * - * Sometimes during resolution, when stability of `x` is retrieved with [isAccessToUnstableLocalVariable], the resolution of `foo` and - * `bar` is not yet finished. Hence, the lambda arg passed to `bar` is not traversed. In this case, the resolution logic first calls - * [visitPostponedAnonymousFunction], then [isAccessToUnstableLocalVariable]. Next, it calls [enterLocalFunction] and starts traversing - * the lambda passed to `bar`. - */ - private val ephemeralConcurrentlyAssignedLocalVariables: MutableSet = mutableSetOf() + // Example of control-flow-postponed lambdas: callBoth({ a.x }, { a = null }) + // Lambdas are called in an unknown order, so control flow edges to both of them go from before the call. + // However, the assignment in the second lambda should invalidate the smart cast in the first. + // + // Example of data-flow-postponed lambdas: genericFunction(run { a = null }, a.x) + // Although in control flow the first lambda always executes before the second argument, in order to determine + // the type arguments to `run` - and thus resolve its argument lambda - we may have to resolve `a.x` first. + // Because smart casts can only get information from statements that have previously been resolved, data flows + // from the result of `run` to the result of `genericFunction` so smart casts should be prohibited in `a.x`. + // + // This mirrors `ControlFlowGraphBuilder.postponedLambdaExits`. + private val postponedLambdas: Stack> = stackOf() - private val functionStack = mutableListOf() + fun reset() { + rootFunction = null + assignedLocalVariablesByFunction = null + postponedLambdas.reset() + functionScopes.reset() + } /** Checks whether the given access is an unstable access to a local variable at this moment. */ - fun isAccessToUnstableLocalVariable(qualifiedAccessExpression: FirQualifiedAccessExpression): Boolean { - val property = qualifiedAccessExpression.referredPropertySymbol?.fir ?: return false - return property in ephemeralConcurrentlyAssignedLocalVariables || property in concurrentlyAssignedLocalVariablesStack.last() - } + @OptIn(DfaInternals::class) + fun isAccessToUnstableLocalVariable(fir: FirExpression): Boolean { + if (assignedLocalVariablesByFunction == null) return false - fun visitPostponedAnonymousFunction(anonymousFunction: FirAnonymousFunction) { - // Postponed anonymous function is visited before the current function call with lambda is resolved. Hence, the invocationKind is - // always null and hence there is no need to check it. In addition, since multiple lambda can be passed, we accumulate the - // effects by appending to `ephemeralConcurrentlyAssignedLocalVariables`. After the function call is resolved, - // `exitAnonymousFunction` will be invoked at some point to properly set up the `persistentConcurrentlyAssignedLocalVariables`. - assignedLocalVariablesByFunction[anonymousFunction.symbol]?.assignedInside?.let { - ephemeralConcurrentlyAssignedLocalVariables.addAll(it) + val realFir = fir.unwrapElement() as? FirQualifiedAccessExpression ?: return false + val property = realFir.referredPropertySymbol?.fir ?: return false + // Have data => have a root function => functionScopes is not empty. + return property in functionScopes.top().second || postponedLambdas.all().any { lambdas -> + // Control-flow-postponed lambdas' assignments should be in `functionScopes.top()`. + // The reason we can't check them here is that one of the entries may be the lambda + // that is currently being analyzed, and assignments in it are, in fact, totally fine. + lambdas.any { (lambda, dataFlowOnly) -> dataFlowOnly && property in lambda.assignedInside } } } - fun finishPostponedAnonymousFunction() { - // Clear the temporarily assigned local variables in `visitPostponedAnonymousFunction`. - ephemeralConcurrentlyAssignedLocalVariables.clear() + private fun getInfoForLocalFunction(symbol: FirFunctionSymbol<*>): FunctionFork? { + val root = rootFunction ?: return null + if (root.symbol == symbol) return null + val cachedMap = assignedLocalVariablesByFunction ?: run { + val data = MiniCfgBuilder.MiniCfgData() + MiniCfgBuilder().visitElement(root, data) + data.functionForks.also { assignedLocalVariablesByFunction = it } + } + return cachedMap[symbol] } - fun enterLocalFunction(function: FirFunction) { - val concurrentlyAssignedLocalVariables = concurrentlyAssignedLocalVariablesStack.last().toMutableSet() - concurrentlyAssignedLocalVariablesStack.add(concurrentlyAssignedLocalVariables) + fun enterFunction(function: FirFunction) { + val prohibitSmartCasts = functionScopes.topOrNull()?.second?.toMutableSet() + ?: mutableSetOf().also { rootFunction = function } - // 1. As mentioned in the comment above, we don't know whether other lambda arguments passed to the same call will be - // called before or after this lambda, so their assignments might have executed. Unless they're not called at all. - // 2. While lambdas from outer calls are not concurrent from control flow point of view, they are concurrent in data flow - // because the way this lambda resolves may affect the way those lambdas resolve, thus we need to forbid dependencies - // from smartcasts in this lambda to statements in these other lambdas. - for (concurrentLambdas in concurrentLambdaArgsStack) { - for (otherLambda in concurrentLambdas) { - if (otherLambda != function && otherLambda.invocationKind != EventOccurrencesRange.ZERO) { - assignedLocalVariablesByFunction[otherLambda.symbol]?.assignedInside?.let { - concurrentlyAssignedLocalVariables += it - } + val info = getInfoForLocalFunction(function.symbol) + for (concurrentLambdas in postponedLambdas.all()) { + for ((otherLambda, dataFlowOnly) in concurrentLambdas) { + if (!dataFlowOnly && otherLambda != info) { + prohibitSmartCasts += otherLambda.assignedInside } } } - - assignedLocalVariablesByFunction[function.symbol]?.let { - functionStack.add(it) - if (function !is FirAnonymousFunction || !function.invocationKind.isInPlace) { - // The function may be called twice concurrently in an SMT environment, which means any assignment it executes - // might in theory happen in between any check it does and a subsequent use of the variable. So if this function - // does any assignments, it cannot smartcast the target variables. - concurrentlyAssignedLocalVariables += it.assignedInside - // The function may also be stored and called later, so assignments done outside its scope after the definition - // might also have executed. - for (outerScope in functionStack) { - concurrentlyAssignedLocalVariables += outerScope.assignedLater + if (function !is FirAnonymousFunction || !function.invocationKind.isInPlace) { + // The function may be stored and then called later => + for ((outerInfo, prohibitInOuterScope) in functionScopes.all()) { + if (info != null) { + // => any access of the variables it touches is no longer smartcastable ever. + // + // TODO: this incorrectly affects separate branches that are visited after this one: + // if (p is Something) { + // if (condition)) { + // foo { p = whatever } + // p.memberOfSomething // Bad + // } else { + // p.memberOfSomething // Marked as an error, but actually OK + // } + // p.memberOfSomething // Bad + // } + // FE1.0 has the same behavior. + prohibitInOuterScope.addAll(info.assignedInside) + } + if (outerInfo != null) { + // => any write to a variable outside the function invalidates smart casts inside it + prohibitSmartCasts += outerInfo.assignedLater } } + if (info != null) { + prohibitSmartCasts.addAll(info.assignedLater) + // => it may be called twice in parallel, so it can't even trust its own assignments + prohibitSmartCasts.addAll(info.assignedInside) + } + } + functionScopes.push(info to prohibitSmartCasts) + } + + fun exitFunction() { + functionScopes.pop() + if (functionScopes.isEmpty) { + rootFunction = null + assignedLocalVariablesByFunction = null } } - fun exitLocalFunction(function: FirFunction) { - concurrentlyAssignedLocalVariablesStack.removeLast() - assignedLocalVariablesByFunction[function.symbol]?.let { - functionStack.popLast() - if (function !is FirAnonymousFunction || !function.invocationKind.isInPlace) { - // The function may be stored and then called later, so any access to the variables it touches - // is no longer smartcastable ever. - // - // TODO: this incorrectly affects separate branches that are visited after this one: - // if (p is Something) { - // if (condition)) { - // foo { p = whatever } - // p.memberOfSomething // Bad - // } else { - // p.memberOfSomething // Marked as an error, but actually OK - // } - // p.memberOfSomething // Bad - // } - // FE1.0 has the same behavior. - for (outerScope in concurrentlyAssignedLocalVariablesStack) { - outerScope += it.assignedInside - } - } - } - } - - fun enterFunctionCall(lambdaArgs: MutableSet, level: Int) { - while (concurrentLambdaArgsStack.size < level) { - // This object is only created on first local anonymous function, so we might have missed some - // `enterFunctionCall`s. None of them have lambda arguments. - concurrentLambdaArgsStack.add(mutableSetOf()) - } - concurrentLambdaArgsStack.add(lambdaArgs) + fun enterFunctionCall(lambdaArgs: Collection) { + // If not inside a function at all, then there is no concept of a local and nothing to track. + if (rootFunction == null) return + postponedLambdas.push(lambdaArgs.mapNotNull { getInfoForLocalFunction(it.symbol) }.associateWithTo(mutableMapOf()) { false }) } fun exitFunctionCall(callCompleted: Boolean) { - // If we had anonymous functions but no calls with lambdas, the stack might have never been initialized. - if (concurrentLambdaArgsStack.isEmpty()) return - - val lambdasInCall = concurrentLambdaArgsStack.popLast() + if (rootFunction == null) return + val lambdasInCall = postponedLambdas.pop() if (!callCompleted) { // TODO: this has the same problem as above: // if (p is Something) { @@ -172,7 +150,10 @@ internal class FirLocalVariableAssignmentAnalyzer( // ) // } // And also as above, FE1.0 produces the same error. - concurrentLambdaArgsStack.lastOrNull()?.addAll(lambdasInCall) + // + // TODO: this should never return null. Also somehow throwing an exception here leads to weird effects: + // apparently the compiler attempts to continue somewhere... + lambdasInCall.keys.associateWithTo(postponedLambdas.topOrNull() ?: return) { true } } } @@ -254,12 +235,6 @@ internal class FirLocalVariableAssignmentAnalyzer( * so that shadowed names are handled correctly. This works because local variables at any scope have higher priority * than members on implicit receivers, even if the implicit receiver is introduced by a later scope. */ - fun analyzeFunction(rootFunction: FirFunction): FirLocalVariableAssignmentAnalyzer { - val data = MiniCfgBuilder.MiniCfgData() - MiniCfgBuilder().visitElement(rootFunction, data) - return FirLocalVariableAssignmentAnalyzer(data.functionForks) - } - class FunctionFork( val assignedLater: Set, val assignedInside: Set, diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt index 959c0730680..c0de1e6ed3e 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/dfa/cfg/ControlFlowGraphBuilder.kt @@ -137,7 +137,7 @@ class ControlFlowGraphBuilder { // ----------------------------------- Regular function ----------------------------------- - fun enterFunction(function: FirFunction): Pair { + fun enterFunction(function: FirFunction): Pair { require(function !is FirAnonymousFunction) val name = when (function) { is FirSimpleFunction -> function.name.asString() @@ -181,7 +181,7 @@ class ControlFlowGraphBuilder { exitTargetsForTry.push(it) } - return Pair(enterNode, localFunctionNode) + return Pair(localFunctionNode, enterNode) } fun exitFunction(function: FirFunction): Pair { diff --git a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt index 69cdbc4e348..7f920e02f1b 100644 --- a/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt +++ b/compiler/fir/resolve/src/org/jetbrains/kotlin/fir/resolve/transformers/body/resolve/FirAbstractBodyResolveTransformerDispatcher.kt @@ -37,7 +37,7 @@ abstract class FirAbstractBodyResolveTransformerDispatcher( ) : FirAbstractBodyResolveTransformer(phase) { final override val context: BodyResolveContext = - outerBodyResolveContext ?: BodyResolveContext(returnTypeCalculator, DataFlowAnalyzerContext.empty(session)) + outerBodyResolveContext ?: BodyResolveContext(returnTypeCalculator, DataFlowAnalyzerContext(session)) final override val components: BodyResolveTransformerComponents = BodyResolveTransformerComponents(session, scopeSession, this, context) diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt index 4e3b787ebbf..8796fcdf8e2 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.fir.kt @@ -10,7 +10,7 @@ fun test_1() { foo( x.length, // stable smartcast run { x = "" }, - x.length // stable smartcast + x.length // can be stable smartcast ) } } @@ -36,3 +36,14 @@ fun test_3() { ) } } + +fun test_4() { + var x: String? = null + if (x != null) { + foo( + x.length, // stable smartcast + run { x = null }, + x.length // either unstable or not a smartcast + ) + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt index afb27c65940..70263f54a4b 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.kt @@ -10,7 +10,7 @@ fun test_1() { foo( x.length, // stable smartcast run { x = "" }, - x.length // stable smartcast + x.length // can be stable smartcast ) } } @@ -36,3 +36,14 @@ fun test_3() { ) } } + +fun test_4() { + var x: String? = null + if (x != null) { + foo( + x.length, // stable smartcast + run { x = null }, + x.length // either unstable or not a smartcast + ) + } +} diff --git a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt index 4a8237b2eff..eac390143b8 100644 --- a/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt +++ b/compiler/testData/diagnostics/testsWithStdLib/smartcasts/lambdaInCallArgs.txt @@ -5,4 +5,5 @@ public fun myRun(/*0*/ block: () -> kotlin.Unit): kotlin.Any? public fun test_1(): kotlin.Unit public fun test_2(): kotlin.Unit public fun test_3(): kotlin.Unit +public fun test_4(): kotlin.Unit