Effects: support called-in-place lambdas

Support initialization of local variables in capture, if that capture is
anonymous lambda that was passed to function which explicitly expressed
its intent to call closure "here and now"

Changes:
- LocalFunctionDeclarationInstruction made open

- Introduce InlinedLocalFunctionDeclarationInstruction, which is subtype
of LocalFunctionDeclarationInstruction with additional semantic: it
is statically known that this function will be called in-place and maybe
with some definite amount of invocations.

- Next-instruction of InlinedLocalFunctionDeclarationInstruction depends
on whether flow can exit its body normally, i.e. on wheter EXIT is
reachable. If flow can reach EXIT, then .next is just straight next
instruction, otherwise it's SINK.

- Take non-local instructions into consideration when analyzing
reachability. We didn't it before because no one ever needed proper
analysis. Now we have InlinedLocalFunctionDeclarationInstruction which
cares about proper reachability of EXIT.

- Pull control-flow information from
InlinedLocalFunctionDeclarationInstruction's EXIT into parent's
pseudocode, thus allowing it to participate in initialization/use
analysis.

- Change logic of 'isCapturedWrite' to not report
"CAPTURED_VAL_INITIALIZATION" if value is captured by the inline-lambda.

==========
Introduction of EffectSystem: 12/18
This commit is contained in:
Dmitry Savvinov
2017-10-03 15:02:44 +03:00
parent 0dd6d1f4c7
commit bdbb161cfa
12 changed files with 194 additions and 58 deletions
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* 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.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -27,9 +28,8 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
interface ControlFlowBuilder { interface ControlFlowBuilder {
// Subroutines // Subroutines
fun enterSubroutine(subroutine: KtElement) fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind? = null)
fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind? = null): Pseudocode
fun exitSubroutine(subroutine: KtElement): Pseudocode
val currentSubroutine: KtElement val currentSubroutine: KtElement
val returnSubroutine: KtElement val returnSubroutine: KtElement
@@ -49,6 +49,8 @@ interface ControlFlowBuilder {
fun declareVariable(property: KtVariableDeclaration) fun declareVariable(property: KtVariableDeclaration)
fun declareFunction(subroutine: KtElement, pseudocode: Pseudocode) fun declareFunction(subroutine: KtElement, pseudocode: Pseudocode)
fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind)
fun declareEntryOrObject(entryOrObject: KtClassOrObject) fun declareEntryOrObject(entryOrObject: KtClassOrObject)
// Labels // Labels
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.* 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.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -134,11 +135,12 @@ abstract class ControlFlowBuilderAdapter : ControlFlowBuilder {
delegateBuilder.exitTryFinally() delegateBuilder.exitTryFinally()
} }
override fun enterSubroutine(subroutine: KtElement) { override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) {
delegateBuilder.enterSubroutine(subroutine) 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 override val currentSubroutine: KtElement
get() = delegateBuilder.currentSubroutine get() = delegateBuilder.currentSubroutine
@@ -178,6 +180,10 @@ abstract class ControlFlowBuilderAdapter : ControlFlowBuilder {
delegateBuilder.declareFunction(subroutine, pseudocode) delegateBuilder.declareFunction(subroutine, pseudocode)
} }
override fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, invocationKind: InvocationKind) {
delegateBuilder.declareInlinedFunction(subroutine, pseudocode, invocationKind)
}
override fun declareEntryOrObject(entryOrObject: KtClassOrObject) { override fun declareEntryOrObject(entryOrObject: KtClassOrObject) {
delegateBuilder.declareEntryOrObject(entryOrObject) delegateBuilder.declareEntryOrObject(entryOrObject)
} }
@@ -369,7 +369,8 @@ class ControlFlowInformationProvider private constructor(
// Do not consider top-level properties // Do not consider top-level properties
if (containingDeclarationDescriptor is PackageFragmentDescriptor) return false if (containingDeclarationDescriptor is PackageFragmentDescriptor) return false
var parentDeclaration = getElementParentDeclaration(writeValueInstruction.element) var parentDeclaration = getElementParentDeclaration(writeValueInstruction.element)
while (true) {
loop@ while (true) {
val context = trace.bindingContext val context = trace.bindingContext
val parentDescriptor = getDeclarationDescriptorIncludingConstructors(context, parentDeclaration) val parentDescriptor = getDeclarationDescriptorIncludingConstructors(context, parentDeclaration)
if (parentDescriptor == containingDeclarationDescriptor) { if (parentDescriptor == containingDeclarationDescriptor) {
@@ -381,6 +382,13 @@ class ControlFlowInformationProvider private constructor(
parentDeclaration = getElementParentDeclaration(parentDeclaration) parentDeclaration = getElementParentDeclaration(parentDeclaration)
} }
is KtDeclarationWithBody -> { 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 if (parentDeclaration is KtFunction && parentDeclaration.isLocal) return true
// miss non-local function or accessor just once // miss non-local function or accessor just once
parentDeclaration = getElementParentDeclaration(parentDeclaration) parentDeclaration = getElementParentDeclaration(parentDeclaration)
@@ -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.AccessTarget
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind 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.*
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
import org.jetbrains.kotlin.diagnostics.Errors.* 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 org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice
import java.util.* import java.util.*
typealias DeferredGenerator = (ControlFlowBuilder) -> Unit
class ControlFlowProcessor(private val trace: BindingTrace) { class ControlFlowProcessor(private val trace: BindingTrace) {
private val builder: ControlFlowBuilder = ControlFlowInstructionsGenerator() private val builder: ControlFlowBuilder = ControlFlowInstructionsGenerator()
fun generatePseudocode(subroutine: KtElement): Pseudocode { fun generatePseudocode(subroutine: KtElement): Pseudocode {
val pseudocode = generate(subroutine) val pseudocode = generate(subroutine, null)
(pseudocode as PseudocodeImpl).postProcess() (pseudocode as PseudocodeImpl).postProcess()
return pseudocode return pseudocode
} }
private fun generate(subroutine: KtElement): Pseudocode { private fun generate(subroutine: KtElement, invocationKind: InvocationKind? = null): Pseudocode {
builder.enterSubroutine(subroutine) builder.enterSubroutine(subroutine, invocationKind)
val cfpVisitor = CFPVisitor(builder) val cfpVisitor = CFPVisitor(builder)
if (subroutine is KtDeclarationWithBody && subroutine !is KtSecondaryConstructor) { if (subroutine is KtDeclarationWithBody && subroutine !is KtSecondaryConstructor) {
val valueParameters = subroutine.valueParameters val valueParameters = subroutine.valueParameters
@@ -87,7 +92,7 @@ class ControlFlowProcessor(private val trace: BindingTrace) {
else { else {
cfpVisitor.generateInstructions(subroutine) cfpVisitor.generateInstructions(subroutine)
} }
return builder.exitSubroutine(subroutine) return builder.exitSubroutine(subroutine, invocationKind)
} }
private fun generateImplicitReturnValue(bodyExpression: KtExpression, subroutine: KtElement) { private fun generateImplicitReturnValue(bodyExpression: KtExpression, subroutine: KtElement) {
@@ -105,7 +110,7 @@ class ControlFlowProcessor(private val trace: BindingTrace) {
val afterDeclaration = builder.createUnboundLabel("after local declaration") val afterDeclaration = builder.createUnboundLabel("after local declaration")
builder.nondeterministicJump(afterDeclaration, subroutine, null) builder.nondeterministicJump(afterDeclaration, subroutine, null)
generate(subroutine) generate(subroutine, null)
builder.bindLabel(afterDeclaration) builder.bindLabel(afterDeclaration)
} }
@@ -115,6 +120,13 @@ class ControlFlowProcessor(private val trace: BindingTrace) {
private val catchFinallyStack = Stack<CatchFinallyLabels>() private val catchFinallyStack = Stack<CatchFinallyLabels>()
// 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<MutableList<DeferredGenerator>>()
private val conditionVisitor = object : KtVisitorVoid() { private val conditionVisitor = object : KtVisitorVoid() {
private fun getSubjectExpression(condition: KtWhenCondition): KtExpression? = private fun getSubjectExpression(condition: KtWhenCondition): KtExpression? =
@@ -1020,14 +1032,42 @@ class ControlFlowProcessor(private val trace: BindingTrace) {
return parent.parent is KtDoWhileExpression return parent.parent is KtDoWhileExpression
} }
private fun visitFunction(function: KtFunction) { private fun visitFunction(function: KtFunction, invocationKind: InvocationKind? = null) {
processLocalDeclaration(function) if (invocationKind == null) {
processLocalDeclaration(function)
}
else {
visitInlinedFunction(function, invocationKind)
}
val isAnonymousFunction = function is KtFunctionLiteral || function.name == null val isAnonymousFunction = function is KtFunctionLiteral || function.name == null
if (isAnonymousFunction || function.isLocal && function.parent !is KtBlockExpression) { if (isAnonymousFunction || function.isLocal && function.parent !is KtBlockExpression) {
builder.createLambda(function) 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) { override fun visitNamedFunction(function: KtNamedFunction) {
visitFunction(function) visitFunction(function)
} }
@@ -1035,7 +1075,11 @@ class ControlFlowProcessor(private val trace: BindingTrace) {
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) { override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
mark(lambdaExpression) mark(lambdaExpression)
val functionLiteral = lambdaExpression.functionLiteral 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) copyValue(functionLiteral, lambdaExpression)
} }
@@ -1485,6 +1529,8 @@ class ControlFlowProcessor(private val trace: BindingTrace) {
val receivers = getReceiverValues(resolvedCall) val receivers = getReceiverValues(resolvedCall)
deferredGeneratorsStack.push(mutableListOf())
var parameterValues = SmartFMap.emptyMap<PseudoValue, ValueParameterDescriptor>() var parameterValues = SmartFMap.emptyMap<PseudoValue, ValueParameterDescriptor>()
for (argument in resolvedCall.call.valueArguments) { for (argument in resolvedCall.call.valueArguments) {
val argumentMapping = resolvedCall.getArgumentMapping(argument) val argumentMapping = resolvedCall.getArgumentMapping(argument)
@@ -1507,7 +1553,10 @@ class ControlFlowProcessor(private val trace: BindingTrace) {
} }
mark(resolvedCall.call.callElement) 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<PseudoValue, ReceiverValue> { private fun getReceiverValues(resolvedCall: ResolvedCall<*>): Map<PseudoValue, ReceiverValue> {
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.cfg.pseudocodeTraverser
import org.jetbrains.kotlin.cfg.ControlFlowInfo import org.jetbrains.kotlin.cfg.ControlFlowInfo
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction 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.LocalFunctionDeclarationInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineEnterInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction
@@ -96,8 +97,13 @@ private fun <I : ControlFlowInfo<*, *>> Pseudocode.collectDataFromSubgraph(
if (instruction is LocalFunctionDeclarationInstruction) { if (instruction is LocalFunctionDeclarationInstruction) {
val subroutinePseudocode = instruction.body val subroutinePseudocode = instruction.body
subroutinePseudocode.collectDataFromSubgraph( subroutinePseudocode.collectDataFromSubgraph(
traversalOrder, edgesMap, mergeEdges, updateEdge, previousInstructions, changed, true) traversalOrder, edgesMap, mergeEdges, updateEdge, previousInstructions, changed, true
val lastInstruction = subroutinePseudocode.getLastInstruction(traversalOrder) )
// 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 previousValue = edgesMap[instruction]
val newValue = edgesMap[lastInstruction] val newValue = edgesMap[lastInstruction]
val updatedValue = newValue?.let { val updatedValue = newValue?.let {
@@ -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.eval.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.* import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.* 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.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
@@ -47,8 +48,8 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() {
private val allBlocks = Stack<BlockInfo>() private val allBlocks = Stack<BlockInfo>()
private fun pushBuilder(scopingElement: KtElement, subroutine: KtElement) { private fun pushBuilder(scopingElement: KtElement, subroutine: KtElement, shouldInline: Boolean) {
val worker = ControlFlowInstructionsGeneratorWorker(scopingElement, subroutine) val worker = ControlFlowInstructionsGeneratorWorker(scopingElement, subroutine, shouldInline)
builders.push(worker) builders.push(worker)
builder = worker builder = worker
} }
@@ -64,32 +65,42 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() {
return worker return worker
} }
override fun enterSubroutine(subroutine: KtElement) { override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) {
val builder = builder val builder = builder
val shouldInlnie = invocationKind != null
if (builder != null && subroutine is KtFunctionLiteral) { if (builder != null && subroutine is KtFunctionLiteral) {
pushBuilder(subroutine, builder.returnSubroutine) pushBuilder(subroutine, builder.returnSubroutine, shouldInlnie)
} }
else { else {
pushBuilder(subroutine, subroutine) pushBuilder(subroutine, subroutine, shouldInlnie)
} }
delegateBuilder.enterBlockScope(subroutine) delegateBuilder.enterBlockScope(subroutine)
delegateBuilder.enterSubroutine(subroutine) delegateBuilder.enterSubroutine(subroutine)
} }
override fun exitSubroutine(subroutine: KtElement): Pseudocode { override fun exitSubroutine(subroutine: KtElement, invocationKind: InvocationKind?): Pseudocode {
super.exitSubroutine(subroutine) super.exitSubroutine(subroutine, invocationKind)
delegateBuilder.exitBlockScope(subroutine) delegateBuilder.exitBlockScope(subroutine)
val worker = popBuilder() val worker = popBuilder()
if (!builders.empty()) { if (!builders.empty()) {
val builder = builders.peek() 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 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 error: Label = pseudocode.createLabel("error", null)
private val sink: Label = pseudocode.createLabel("sink", null) private val sink: Label = pseudocode.createLabel("sink", null)
@@ -145,7 +156,7 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() {
override val currentLoop: KtLoopExpression? override val currentLoop: KtLoopExpression?
get() = if (loopInfo.empty()) null else loopInfo.peek().element get() = if (loopInfo.empty()) null else loopInfo.peek().element
override fun enterSubroutine(subroutine: KtElement) { override fun enterSubroutine(subroutine: KtElement, invocationKind: InvocationKind?) {
val blockInfo = SubroutineInfo( val blockInfo = SubroutineInfo(
subroutine, subroutine,
/* entry point */ createUnboundLabel(), /* 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) } getSubroutineExitPoint(subroutine)?.let { bindLabel(it) }
pseudocode.addExitInstruction(SubroutineExitInstruction(subroutine, currentScope, false)) pseudocode.addExitInstruction(SubroutineExitInstruction(subroutine, currentScope, false))
bindLabel(error) bindLabel(error)
@@ -261,6 +272,10 @@ class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() {
add(LocalFunctionDeclarationInstruction(subroutine, pseudocode, currentScope)) 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) { override fun declareEntryOrObject(entryOrObject: KtClassOrObject) {
add(VariableDeclarationInstruction(entryOrObject, currentScope)) add(VariableDeclarationInstruction(entryOrObject, currentScope))
} }
@@ -45,6 +45,7 @@ interface Pseudocode {
val enterInstruction: SubroutineEnterInstruction val enterInstruction: SubroutineEnterInstruction
val isInlined: Boolean
val containsDoWhile: Boolean val containsDoWhile: Boolean
val rootPseudocode: Pseudocode val rootPseudocode: Pseudocode
@@ -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.AbstractJumpInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ConditionalJumpInstruction 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.jumps.NondeterministicJumpInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.LocalFunctionDeclarationInstruction import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.*
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.pseudocodeTraverser.TraversalOrder.BACKWARD import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.BACKWARD
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult 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 org.jetbrains.kotlin.psi.KtElement
import java.util.* 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<Instruction>() internal val mutableInstructionList = ArrayList<Instruction>()
override val instructions = ArrayList<Instruction>() override val instructions = ArrayList<Instruction>()
@@ -56,6 +53,8 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode
getLocalDeclarations(this) getLocalDeclarations(this)
} }
val reachableInstructions = hashSetOf<Instruction>()
private val representativeInstructions = HashMap<KtElement, KtElementInstruction>() private val representativeInstructions = HashMap<KtElement, KtElementInstruction>()
private val labels = ArrayList<PseudocodeLabel>() private val labels = ArrayList<PseudocodeLabel>()
@@ -110,7 +109,7 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode
override val reversedInstructions: List<Instruction> override val reversedInstructions: List<Instruction>
get() { get() {
val traversedInstructions = linkedSetOf<Instruction>() val traversedInstructions = linkedSetOf<Instruction>()
traverseFollowingInstructions(sinkInstruction, traversedInstructions, BACKWARD, null) traverseFollowingInstructions(if (this.isInlined) instructions.last() else sinkInstruction, traversedInstructions, BACKWARD, null)
if (traversedInstructions.size < instructions.size) { if (traversedInstructions.size < instructions.size) {
val simplyReversedInstructions = instructions.reversed() val simplyReversedInstructions = instructions.reversed()
for (instruction in simplyReversedInstructions) { for (instruction in simplyReversedInstructions) {
@@ -224,22 +223,17 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode
postPrecessed = true postPrecessed = true
errorInstruction.sink = sinkInstruction errorInstruction.sink = sinkInstruction
exitInstruction.sink = sinkInstruction exitInstruction.sink = sinkInstruction
for ((index, instruction) in mutableInstructionList.withIndex()) { 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) 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() collectAndCacheReachableInstructions()
for (localFunctionDeclarationInstruction in localDeclarations) {
(localFunctionDeclarationInstruction.body as PseudocodeImpl).collectAndCacheReachableInstructions()
}
} }
private fun collectAndCacheReachableInstructions() { private fun collectAndCacheReachableInstructions() {
val reachableInstructions = collectReachableInstructions() collectReachableInstructions()
for (instruction in mutableInstructionList) { for (instruction in mutableInstructionList) {
if (reachableInstructions.contains(instruction)) { if (reachableInstructions.contains(instruction)) {
instructions.add(instruction) instructions.add(instruction)
@@ -287,6 +281,14 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode
instruction.next = sinkInstruction 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) { override fun visitSubroutineExit(instruction: SubroutineExitInstruction) {
// Nothing // Nothing
} }
@@ -301,25 +303,25 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode
}) })
} }
private fun collectReachableInstructions(): Set<Instruction> { private fun collectReachableInstructions() {
val visited = hashSetOf<Instruction>() val reachableFromThisPseudocode = hashSetOf<Instruction>()
traverseFollowingInstructions(enterInstruction, visited, FORWARD traverseFollowingInstructions(enterInstruction, reachableFromThisPseudocode, FORWARD
) { instruction -> ) { instruction ->
if (instruction is MagicInstruction && instruction.kind === MagicKind.EXHAUSTIVE_WHEN_ELSE) { if (instruction is MagicInstruction && instruction.kind === MagicKind.EXHAUSTIVE_WHEN_ELSE) {
return@traverseFollowingInstructions TraverseInstructionResult.SKIP return@traverseFollowingInstructions TraverseInstructionResult.SKIP
} }
TraverseInstructionResult.CONTINUE 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) reachableFromThisPseudocode.forEach { (it.owner as PseudocodeImpl).reachableInstructions.add(it) }
}
if (!visited.contains(sinkInstruction)) {
visited.add(sinkInstruction)
}
return visited
} }
private fun markDeadInstructions() { private fun markDeadInstructions() {
@@ -343,7 +345,7 @@ class PseudocodeImpl(override val correspondingElement: KtElement) : Pseudocode
} }
override fun copy(): PseudocodeImpl { override fun copy(): PseudocodeImpl {
val result = PseudocodeImpl(correspondingElement) val result = PseudocodeImpl(correspondingElement, isInlined)
result.repeatWhole(this) result.repeatWhole(this)
return result return result
} }
@@ -33,6 +33,10 @@ open class InstructionVisitor {
visitInstructionWithNext(instruction) visitInstructionWithNext(instruction)
} }
open fun visitInlinedLocalFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction) {
visitLocalFunctionDeclarationInstruction(instruction)
}
open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction) { open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction) {
visitInstructionWithNext(instruction) visitInstructionWithNext(instruction)
} }
@@ -30,6 +30,9 @@ abstract class InstructionVisitorWithResult<out R> {
open fun visitLocalFunctionDeclarationInstruction(instruction: LocalFunctionDeclarationInstruction): R = open fun visitLocalFunctionDeclarationInstruction(instruction: LocalFunctionDeclarationInstruction): R =
visitInstructionWithNext(instruction) visitInstructionWithNext(instruction)
open fun visitInlinedFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction): R =
visitLocalFunctionDeclarationInstruction(instruction)
open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction): R = visitInstructionWithNext(instruction) open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction): R = visitInstructionWithNext(instruction)
open fun visitUnconditionalJump(instruction: UnconditionalJumpInstruction): R = visitJump(instruction) open fun visitUnconditionalJump(instruction: UnconditionalJumpInstruction): R = visitJump(instruction)
@@ -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 <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitInlinedFunctionDeclarationInstruction(this)
override fun toString(): String = "inlined(${render(element)})"
}
@@ -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.InstructionVisitorWithResult
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl
class LocalFunctionDeclarationInstruction( open class LocalFunctionDeclarationInstruction(
element: KtElement, element: KtElement,
val body: Pseudocode, val body: Pseudocode,
blockScope: BlockScope blockScope: BlockScope