diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt index a92dea36fc4..bed554d724b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilder.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* +import org.jetbrains.kotlin.contracts.description.InvocationKind import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall @@ -27,9 +28,8 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue interface ControlFlowBuilder { // Subroutines - fun enterSubroutine(subroutine: KtElement) - - fun exitSubroutine(subroutine: KtElement): Pseudocode + fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind? = null) + fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind? = null): Pseudocode val currentSubroutine: KtElement val returnSubroutine: KtElement @@ -49,6 +49,8 @@ interface ControlFlowBuilder { fun declareVariable(property: KtVariableDeclaration) fun declareFunction(subroutine: KtElement, pseudocode: Pseudocode) + fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind) + fun declareEntryOrObject(entryOrObject: KtClassOrObject) // Labels diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt index b71dbba01c5..4957f148fcc 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowBuilderAdapter.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* +import org.jetbrains.kotlin.contracts.description.InvocationKind import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall @@ -134,11 +135,12 @@ abstract class ControlFlowBuilderAdapter : ControlFlowBuilder { delegateBuilder.exitTryFinally() } - override fun enterSubroutine(subroutine: KtElement) { - delegateBuilder.enterSubroutine(subroutine) + override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) { + delegateBuilder.enterSubroutine(subroutine, invocationKind) } - override fun exitSubroutine(subroutine: KtElement): Pseudocode = delegateBuilder.exitSubroutine(subroutine) + override fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind?): Pseudocode = + delegateBuilder.exitSubroutine(subroutine, invocationKind) override val currentSubroutine: KtElement get() = delegateBuilder.currentSubroutine @@ -178,6 +180,10 @@ abstract class ControlFlowBuilderAdapter : ControlFlowBuilder { delegateBuilder.declareFunction(subroutine, pseudocode) } + override fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind) { + delegateBuilder.declareInlinedFunction(subroutine, pseudocode, invocationKind) + } + override fun declareEntryOrObject(entryOrObject: KtClassOrObject) { delegateBuilder.declareEntryOrObject(entryOrObject) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt index 94af80a4eb0..9c2f3afa34b 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowInformationProvider.kt @@ -369,7 +369,8 @@ class ControlFlowInformationProvider private constructor( // Do not consider top-level properties if (containingDeclarationDescriptor is PackageFragmentDescriptor) return false var parentDeclaration = getElementParentDeclaration(writeValueInstruction.element) - while (true) { + + loop@ while (true) { val context = trace.bindingContext val parentDescriptor = getDeclarationDescriptorIncludingConstructors(context, parentDeclaration) if (parentDescriptor == containingDeclarationDescriptor) { @@ -381,6 +382,13 @@ class ControlFlowInformationProvider private constructor( parentDeclaration = getElementParentDeclaration(parentDeclaration) } is KtDeclarationWithBody -> { + // If it is captured write in lambda that is called in-place, then skip it (treat as parent) + val maybeEnclosingLambdaExpr = parentDeclaration.parent + if (maybeEnclosingLambdaExpr is KtLambdaExpression && trace[BindingContext.LAMBDA_INVOCATIONS, maybeEnclosingLambdaExpr] != null) { + parentDeclaration = getElementParentDeclaration(parentDeclaration) + continue@loop + } + if (parentDeclaration is KtFunction && parentDeclaration.isLocal) return true // miss non-local function or accessor just once parentDeclaration = getElementParentDeclaration(parentDeclaration) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt index 081718f7685..3afaeb581d8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/ControlFlowProcessor.kt @@ -30,6 +30,9 @@ import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeImpl import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.AccessTarget import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind +import org.jetbrains.kotlin.contracts.description.InvocationKind +import org.jetbrains.kotlin.contracts.description.canBeRevisited +import org.jetbrains.kotlin.contracts.description.isDefinitelyVisited import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.diagnostics.Errors.* @@ -58,18 +61,20 @@ import org.jetbrains.kotlin.types.expressions.OperatorConventions import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice import java.util.* +typealias DeferredGenerator = (ControlFlowBuilder) -> Unit + class ControlFlowProcessor(private val trace: BindingTrace) { private val builder: ControlFlowBuilder = ControlFlowInstructionsGenerator() fun generatePseudocode(subroutine: KtElement): Pseudocode { - val pseudocode = generate(subroutine) + val pseudocode = generate(subroutine, null) (pseudocode as PseudocodeImpl).postProcess() return pseudocode } - private fun generate(subroutine: KtElement): Pseudocode { - builder.enterSubroutine(subroutine) + private fun generate(subroutine: KtElement, invocationKind: InvocationKind? = null): Pseudocode { + builder.enterSubroutine(subroutine, invocationKind) val cfpVisitor = CFPVisitor(builder) if (subroutine is KtDeclarationWithBody && subroutine !is KtSecondaryConstructor) { val valueParameters = subroutine.valueParameters @@ -87,7 +92,7 @@ class ControlFlowProcessor(private val trace: BindingTrace) { else { cfpVisitor.generateInstructions(subroutine) } - return builder.exitSubroutine(subroutine) + return builder.exitSubroutine(subroutine, invocationKind) } private fun generateImplicitReturnValue(bodyExpression: KtExpression, subroutine: KtElement) { @@ -105,7 +110,7 @@ class ControlFlowProcessor(private val trace: BindingTrace) { val afterDeclaration = builder.createUnboundLabel("after local declaration") builder.nondeterministicJump(afterDeclaration, subroutine, null) - generate(subroutine) + generate(subroutine, null) builder.bindLabel(afterDeclaration) } @@ -115,6 +120,13 @@ class ControlFlowProcessor(private val trace: BindingTrace) { private val catchFinallyStack = Stack() + // Some language constructs (e.g. inlined lambdas) should be partially processed before call + // (to provide argument for call itself), and partially - after (in case of inlined lambdas, + // their body should be generated after call). To do so, we store deferred generators, which + // will be called after call instruction is emitted. + // Stack is necessary to store generators across nested calls + private val deferredGeneratorsStack = Stack>() + private val conditionVisitor = object : KtVisitorVoid() { private fun getSubjectExpression(condition: KtWhenCondition): KtExpression? = @@ -1020,14 +1032,42 @@ class ControlFlowProcessor(private val trace: BindingTrace) { return parent.parent is KtDoWhileExpression } - private fun visitFunction(function: KtFunction) { - processLocalDeclaration(function) + private fun visitFunction(function: KtFunction, invocationKind: InvocationKind? = null) { + if (invocationKind == null) { + processLocalDeclaration(function) + } + else { + visitInlinedFunction(function, invocationKind) + } + val isAnonymousFunction = function is KtFunctionLiteral || function.name == null if (isAnonymousFunction || function.isLocal && function.parent !is KtBlockExpression) { builder.createLambda(function) } } + private fun visitInlinedFunction(lambdaFunctionLiteral: KtFunction, invocationKind: InvocationKind) { + // Defer emitting of inlined declaration + deferredGeneratorsStack.peek().add({ builder -> + val beforeDeclaration = builder.createUnboundLabel("before inlined declaration") + val afterDeclaration = builder.createUnboundLabel("after inlined declaration") + + builder.bindLabel(beforeDeclaration) + + if (!invocationKind.isDefinitelyVisited()) { + builder.nondeterministicJump(afterDeclaration, lambdaFunctionLiteral, null) + } + + generate(lambdaFunctionLiteral, invocationKind) + + if (invocationKind.canBeRevisited()) { + builder.nondeterministicJump(beforeDeclaration, lambdaFunctionLiteral, null) + } + + builder.bindLabel(afterDeclaration) + }) + } + override fun visitNamedFunction(function: KtNamedFunction) { visitFunction(function) } @@ -1035,7 +1075,11 @@ class ControlFlowProcessor(private val trace: BindingTrace) { override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) { mark(lambdaExpression) val functionLiteral = lambdaExpression.functionLiteral - visitFunction(functionLiteral) + + // NB. Behaviour here is implicitly controlled by the LanguageFeature 'CallsInPlaceEffect' + // If this feature is turned off, then slice LAMBDA_INVOCATIONS is never written and invocationKind + // in all subsequent calls always 'null', resulting in falling back to old behaviour + visitFunction(functionLiteral, trace[BindingContext.LAMBDA_INVOCATIONS, lambdaExpression]) copyValue(functionLiteral, lambdaExpression) } @@ -1485,6 +1529,8 @@ class ControlFlowProcessor(private val trace: BindingTrace) { val receivers = getReceiverValues(resolvedCall) + deferredGeneratorsStack.push(mutableListOf()) + var parameterValues = SmartFMap.emptyMap() for (argument in resolvedCall.call.valueArguments) { val argumentMapping = resolvedCall.getArgumentMapping(argument) @@ -1507,7 +1553,10 @@ class ControlFlowProcessor(private val trace: BindingTrace) { } mark(resolvedCall.call.callElement) - return builder.call(callElement, resolvedCall, receivers, parameterValues) + val callInstruction = builder.call(callElement, resolvedCall, receivers, parameterValues) + val deferredGeneratorsForCall = deferredGeneratorsStack.pop() + deferredGeneratorsForCall.forEach { it.invoke(builder) } + return callInstruction } private fun getReceiverValues(resolvedCall: ResolvedCall<*>): Map { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt index 2cf67c49703..5d066880518 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/PseudocodeTraverser.kt @@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg.pseudocodeTraverser import org.jetbrains.kotlin.cfg.ControlFlowInfo import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction +import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.InlinedLocalFunctionDeclarationInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction @@ -96,8 +97,13 @@ private fun > Pseudocode.collectDataFromSubgraph( if (instruction is LocalFunctionDeclarationInstruction) { val subroutinePseudocode = instruction.body subroutinePseudocode.collectDataFromSubgraph( - traversalOrder, edgesMap, mergeEdges, updateEdge, previousInstructions, changed, true) - val lastInstruction = subroutinePseudocode.getLastInstruction(traversalOrder) + traversalOrder, edgesMap, mergeEdges, updateEdge, previousInstructions, changed, true + ) + // Special case for inlined functions: take flow from EXIT instructions (it contains flow which exits declaration normally) + val lastInstruction = if (instruction is InlinedLocalFunctionDeclarationInstruction && traversalOrder == FORWARD) + subroutinePseudocode.exitInstruction + else + subroutinePseudocode.getLastInstruction(traversalOrder) val previousValue = edgesMap[instruction] val newValue = edgesMap[lastInstruction] val updatedValue = newValue?.let { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt index 487edc5d865..fba85f518ed 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/ControlFlowInstructionsGenerator.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.* +import org.jetbrains.kotlin.contracts.description.InvocationKind import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall @@ -47,8 +48,8 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { private val allBlocks = Stack() - private fun pushBuilder(scopingElement: KtElement, subroutine: KtElement) { - val worker = ControlFlowInstructionsGeneratorWorker(scopingElement, subroutine) + private fun pushBuilder(scopingElement: KtElement, subroutine: KtElement, shouldInline: Boolean) { + val worker = ControlFlowInstructionsGeneratorWorker(scopingElement, subroutine, shouldInline) builders.push(worker) builder = worker } @@ -64,32 +65,42 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { return worker } - override fun enterSubroutine(subroutine: KtElement) { + override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) { val builder = builder + val shouldInlnie = invocationKind != null if (builder != null && subroutine is KtFunctionLiteral) { - pushBuilder(subroutine, builder.returnSubroutine) + pushBuilder(subroutine, builder.returnSubroutine, shouldInlnie) } else { - pushBuilder(subroutine, subroutine) + pushBuilder(subroutine, subroutine, shouldInlnie) } delegateBuilder.enterBlockScope(subroutine) delegateBuilder.enterSubroutine(subroutine) } - override fun exitSubroutine(subroutine: KtElement): Pseudocode { - super.exitSubroutine(subroutine) + override fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind?): Pseudocode { + super.exitSubroutine(subroutine, invocationKind) delegateBuilder.exitBlockScope(subroutine) val worker = popBuilder() if (!builders.empty()) { val builder = builders.peek() - builder.declareFunction(subroutine, worker.pseudocode) + if (invocationKind == null) { + builder.declareFunction(subroutine, worker.pseudocode) + } + else { + builder.declareInlinedFunction(subroutine, worker.pseudocode, invocationKind) + } } return worker.pseudocode } - private inner class ControlFlowInstructionsGeneratorWorker(scopingElement: KtElement, override val returnSubroutine: KtElement) : ControlFlowBuilder { + private inner class ControlFlowInstructionsGeneratorWorker( + scopingElement: KtElement, + override val returnSubroutine: KtElement, + shouldInline: Boolean + ) : ControlFlowBuilder { - val pseudocode: PseudocodeImpl = PseudocodeImpl(scopingElement) + val pseudocode: PseudocodeImpl = PseudocodeImpl(scopingElement, shouldInline) private val error: Label = pseudocode.createLabel("error", null) private val sink: Label = pseudocode.createLabel("sink", null) @@ -145,7 +156,7 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { override val currentLoop: KtLoopExpression? get() = if (loopInfo.empty()) null else loopInfo.peek().element - override fun enterSubroutine(subroutine: KtElement) { + override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) { val blockInfo = SubroutineInfo( subroutine, /* entry point */ createUnboundLabel(), @@ -204,7 +215,7 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { } } - override fun exitSubroutine(subroutine: KtElement): Pseudocode { + override fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind?): Pseudocode { getSubroutineExitPoint(subroutine)?.let { bindLabel(it) } pseudocode.addExitInstruction(SubroutineExitInstruction(subroutine, currentScope, false)) bindLabel(error) @@ -261,6 +272,10 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() { add(LocalFunctionDeclarationInstruction(subroutine, pseudocode, currentScope)) } + override fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind) { + add(InlinedLocalFunctionDeclarationInstruction(subroutine, pseudocode, currentScope, invocationKind)) + } + override fun declareEntryOrObject(entryOrObject: KtClassOrObject) { add(VariableDeclarationInstruction(entryOrObject, currentScope)) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt index 7ab4903baf6..12820e0fd8d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/Pseudocode.kt @@ -45,6 +45,7 @@ interface Pseudocode { val enterInstruction: SubroutineEnterInstruction + val isInlined: Boolean val containsDoWhile: Boolean val rootPseudocode: Pseudocode diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt index cb7972476cf..82647f1afaf 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/PseudocodeImpl.kt @@ -27,10 +27,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MergeInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.AbstractJumpInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ConditionalJumpInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.NondeterministicJumpInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction -import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction +import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.* import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.BACKWARD import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult @@ -38,7 +35,7 @@ import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverseFollowingInstruction import org.jetbrains.kotlin.psi.KtElement import java.util.* -class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode { +class PseudocodeImpl(override val correspondingElement: KtElement, override val isInlined: Boolean) : Pseudocode { internal val mutableInstructionList = ArrayList() override val instructions = ArrayList() @@ -56,6 +53,8 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode getLocalDeclarations(this) } + val reachableInstructions = hashSetOf() + private val representativeInstructions = HashMap() private val labels = ArrayList() @@ -110,7 +109,7 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode override val reversedInstructions: List get() { val traversedInstructions = linkedSetOf() - traverseFollowingInstructions(sinkInstruction, traversedInstructions, BACKWARD, null) + traverseFollowingInstructions(if (this.isInlined) instructions.last() else sinkInstruction, traversedInstructions, BACKWARD, null) if (traversedInstructions.size < instructions.size) { val simplyReversedInstructions = instructions.reversed() for (instruction in simplyReversedInstructions) { @@ -224,22 +223,17 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode postPrecessed = true errorInstruction.sink = sinkInstruction exitInstruction.sink = sinkInstruction + for ((index, instruction) in mutableInstructionList.withIndex()) { - //recursively invokes 'postProcess' for local declarations + //recursively invokes 'postProcess' for local declarations, thus it needs global set of reachable instructions instruction.processInstruction(index) } - if (parent != null) return - // Collecting reachable instructions should be done after processing all instructions - // (including instructions in local declarations) to avoid being in incomplete state. collectAndCacheReachableInstructions() - for (localFunctionDeclarationInstruction in localDeclarations) { - (localFunctionDeclarationInstruction.body as PseudocodeImpl).collectAndCacheReachableInstructions() - } } private fun collectAndCacheReachableInstructions() { - val reachableInstructions = collectReachableInstructions() + collectReachableInstructions() for (instruction in mutableInstructionList) { if (reachableInstructions.contains(instruction)) { instructions.add(instruction) @@ -287,6 +281,14 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode instruction.next = sinkInstruction } + override fun visitInlinedLocalFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction) { + val body = instruction.body as PseudocodeImpl + body.parent = this@PseudocodeImpl + body.postProcess() + // Don't add edge to next instruction if flow can't reach exit of inlined declaration + instruction.next = if (body.instructions.contains(body.exitInstruction)) getNextPosition(currentPosition) else sinkInstruction + } + override fun visitSubroutineExit(instruction: SubroutineExitInstruction) { // Nothing } @@ -301,25 +303,25 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode }) } - private fun collectReachableInstructions(): Set { - val visited = hashSetOf() - traverseFollowingInstructions(enterInstruction, visited, FORWARD + private fun collectReachableInstructions() { + val reachableFromThisPseudocode = hashSetOf() + traverseFollowingInstructions(enterInstruction, reachableFromThisPseudocode, FORWARD ) { instruction -> if (instruction is MagicInstruction && instruction.kind === MagicKind.EXHAUSTIVE_WHEN_ELSE) { return@traverseFollowingInstructions TraverseInstructionResult.SKIP } TraverseInstructionResult.CONTINUE } - if (!visited.contains(exitInstruction)) { - visited.add(exitInstruction) + + // Don't force-add EXIT and ERROR for inlined pseudocodes because for such + // declarations those instructions has special semantic + if (!isInlined) { + reachableFromThisPseudocode.add(exitInstruction) + reachableFromThisPseudocode.add(errorInstruction) + reachableFromThisPseudocode.add(sinkInstruction) } - if (!visited.contains(errorInstruction)) { - visited.add(errorInstruction) - } - if (!visited.contains(sinkInstruction)) { - visited.add(sinkInstruction) - } - return visited + + reachableFromThisPseudocode.forEach { (it.owner as PseudocodeImpl).reachableInstructions.add(it) } } private fun markDeadInstructions() { @@ -343,7 +345,7 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode } override fun copy(): PseudocodeImpl { - val result = PseudocodeImpl(correspondingElement) + val result = PseudocodeImpl(correspondingElement, isInlined) result.repeatWhole(this) return result } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt index f1cd8c76ac8..aa872381d84 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitor.kt @@ -33,6 +33,10 @@ open class InstructionVisitor { visitInstructionWithNext(instruction) } + open fun visitInlinedLocalFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction) { + visitLocalFunctionDeclarationInstruction(instruction) + } + open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction) { visitInstructionWithNext(instruction) } diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt index 9b160cb234d..a5b9b96f947 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/InstructionVisitorWithResult.kt @@ -30,6 +30,9 @@ abstract class InstructionVisitorWithResult { open fun visitLocalFunctionDeclarationInstruction(instruction: LocalFunctionDeclarationInstruction): R = visitInstructionWithNext(instruction) + open fun visitInlinedFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction): R = + visitLocalFunctionDeclarationInstruction(instruction) + open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction): R = visitInstructionWithNext(instruction) open fun visitUnconditionalJump(instruction: UnconditionalJumpInstruction): R = visitJump(instruction) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/InlinedLocalFunctionDeclarationInstruction.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/InlinedLocalFunctionDeclarationInstruction.kt new file mode 100644 index 00000000000..ce3b645e207 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/InlinedLocalFunctionDeclarationInstruction.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.cfg.pseudocode.instructions.special + +import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode +import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope +import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl +import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor +import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult +import org.jetbrains.kotlin.contracts.description.InvocationKind +import org.jetbrains.kotlin.psi.KtElement + +class InlinedLocalFunctionDeclarationInstruction( + element: KtElement, + body: Pseudocode, + blockScope: BlockScope, + val kind: InvocationKind +) : LocalFunctionDeclarationInstruction(element, body, blockScope) { + override fun createCopy(): InstructionImpl = InlinedLocalFunctionDeclarationInstruction(element, body, blockScope, kind) + + override fun accept(visitor: InstructionVisitor) = visitor.visitInlinedLocalFunctionDeclarationInstruction(this) + + override fun accept(visitor: InstructionVisitorWithResult): R = visitor.visitInlinedFunctionDeclarationInstruction(this) + + override fun toString(): String = "inlined(${render(element)})" +} \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt index f7ac71e85ea..88e1d5f3062 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/cfg/pseudocode/instructions/special/LocalFunctionDeclarationInstruction.kt @@ -25,7 +25,7 @@ import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl -class LocalFunctionDeclarationInstruction( +open class LocalFunctionDeclarationInstruction( element: KtElement, val body: Pseudocode, blockScope: BlockScope