Extract control flow analysis to separate module

Extract a service interface out of ControlFlowInformationProviderImpl
and register its implementation in two "leaf" modules: 'cli',
'idea-core'.

This improves parallel build, since a lot of modules depend on
'frontend' but only these two modules reference the implementation and
thus depend on the full CFA implementation now.
This commit is contained in:
Alexander Udalov
2020-03-16 01:13:51 +01:00
committed by Alexander Udalov
parent c744515832
commit 2e2caae05c
70 changed files with 113 additions and 29 deletions
+15
View File
@@ -0,0 +1,15 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
compile(project(":compiler:frontend"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeJars("guava", rootProject = rootProject) }
}
sourceSets {
"main" { projectDefault() }
"test" {}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2010-2015 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;
public abstract class BlockInfo {}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.psi.KtElement
import java.util.Collections
abstract class BreakableBlockInfo(open val element: KtElement, val entryPoint: Label, val exitPoint: Label) : BlockInfo() {
val referablePoints: MutableSet<Label> = hashSetOf()
init {
markReferablePoints(entryPoint, exitPoint)
}
protected fun markReferablePoints(vararg labels: Label) {
Collections.addAll(referablePoints, *labels)
}
}
@@ -0,0 +1,192 @@
/*
* Copyright 2010-2015 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
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.ReadValueInstruction
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
import org.jetbrains.kotlin.cfg.variable.PseudocodeVariablesData
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.types.expressions.OperatorConventions
class ConstructorConsistencyChecker private constructor(
private val classOrObject: KtClassOrObject,
private val classDescriptor: ClassDescriptor,
private val trace: BindingTrace,
private val pseudocode: Pseudocode,
private val variablesData: PseudocodeVariablesData
) {
private val finalClass = classDescriptor.isFinalClass
private fun insideLValue(reference: KtReferenceExpression): Boolean {
val binary = reference.getStrictParentOfType<KtBinaryExpression>() ?: return false
if (binary.operationToken in KtTokens.ALL_ASSIGNMENTS) {
val binaryLeft = binary.left
var current: PsiElement = reference
while (current !== binaryLeft && current !== binary) {
current = current.parent ?: return false
}
return current === binaryLeft
}
return false
}
private fun safeReferenceUsage(reference: KtReferenceExpression): Boolean {
val descriptor = trace.get(BindingContext.REFERENCE_TARGET, reference)
if (descriptor is PropertyDescriptor) {
if (!finalClass && descriptor.isOverridable) {
trace.record(BindingContext.LEAKING_THIS, reference, LeakingThisDescriptor.NonFinalProperty(descriptor, classOrObject))
return true
}
if (descriptor.containingDeclaration != classDescriptor) return true
return if (insideLValue(reference)) descriptor.setter?.isDefault != false else descriptor.getter?.isDefault != false
}
return true
}
private fun safeThisUsage(expression: KtThisExpression): Boolean {
val referenceDescriptor = trace.get(BindingContext.REFERENCE_TARGET, expression.instanceReference)
if (referenceDescriptor != classDescriptor) return true
val parent = expression.parent
return when (parent) {
is KtQualifiedExpression -> (parent.selectorExpression as? KtSimpleNameExpression)?.let { safeReferenceUsage(it) } ?: false
is KtBinaryExpression -> OperatorConventions.IDENTITY_EQUALS_OPERATIONS.contains(parent.operationToken)
else -> false
}
}
private fun safeCallUsage(expression: KtCallExpression): Boolean {
val callee = expression.calleeExpression
if (callee is KtReferenceExpression) {
val descriptor = trace.get(BindingContext.REFERENCE_TARGET, callee)
if (descriptor is FunctionDescriptor) {
val containingDescriptor = descriptor.containingDeclaration
if (containingDescriptor != classDescriptor) return true
if (!finalClass && descriptor.isOverridable) {
trace.record(BindingContext.LEAKING_THIS, callee, LeakingThisDescriptor.NonFinalFunction(descriptor, classOrObject))
return true
}
}
}
return false
}
fun check() {
// List of properties to initialize
val propertyDescriptors = variablesData.getDeclaredVariables(pseudocode, false)
.filterIsInstance<PropertyDescriptor>()
.filter { trace.get(BindingContext.BACKING_FIELD_REQUIRED, it) == true }
pseudocode.traverse(
TraversalOrder.FORWARD, variablesData.variableInitializers
) { instruction, enterData, _ ->
fun firstUninitializedNotNullProperty() = propertyDescriptors.firstOrNull {
!it.type.isMarkedNullable && !KotlinBuiltIns.isPrimitiveType(it.type) &&
!it.isLateInit && !(enterData.getOrNull(it)?.definitelyInitialized() ?: false)
}
fun handleLeakingThis(expression: KtExpression) {
if (!finalClass) {
trace.record(
BindingContext.LEAKING_THIS, target(expression),
LeakingThisDescriptor.NonFinalClass(classDescriptor, classOrObject)
)
} else {
val uninitializedProperty = firstUninitializedNotNullProperty()
if (uninitializedProperty != null) {
trace.record(
BindingContext.LEAKING_THIS, target(expression),
LeakingThisDescriptor.PropertyIsNull(uninitializedProperty, classOrObject)
)
}
}
}
if (instruction.owner != pseudocode) {
return@traverse
}
if (instruction is KtElementInstruction) {
val element = instruction.element
when (instruction) {
is ReadValueInstruction ->
if (element is KtThisExpression) {
if (!safeThisUsage(element)) {
handleLeakingThis(element)
}
}
is MagicInstruction ->
if (instruction.kind == MagicKind.IMPLICIT_RECEIVER) {
if (element is KtCallExpression) {
if (!safeCallUsage(element)) {
handleLeakingThis(element)
}
} else if (element is KtReferenceExpression) {
if (!safeReferenceUsage(element)) {
handleLeakingThis(element)
}
}
}
}
}
}
}
companion object {
@JvmStatic
fun check(
constructor: KtSecondaryConstructor,
trace: BindingTrace,
pseudocode: Pseudocode,
pseudocodeVariablesData: PseudocodeVariablesData
) = check(constructor.getContainingClassOrObject(), trace, pseudocode, pseudocodeVariablesData)
@JvmStatic
fun check(
classOrObject: KtClassOrObject,
trace: BindingTrace,
pseudocode: Pseudocode,
pseudocodeVariablesData: PseudocodeVariablesData
) {
val classDescriptor = trace.get(BindingContext.CLASS, classOrObject) ?: return
ConstructorConsistencyChecker(classOrObject, classDescriptor, trace, pseudocode, pseudocodeVariablesData).check()
}
private fun target(expression: KtExpression): KtExpression = when (expression) {
is KtThisExpression -> {
val selectorOrThis = (expression.parent as? KtQualifiedExpression)?.let {
if (it.receiverExpression === expression) it.selectorExpression else null
} ?: expression
if (selectorOrThis === expression) selectorOrThis else target(selectorOrThis)
}
is KtCallExpression -> expression.let { it.calleeExpression ?: it }
else -> expression
}
}
}
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2015 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
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.EventOccurrencesRange
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
interface ControlFlowBuilder {
// Subroutines
fun enterSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange? = null)
fun exitSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange? = null): Pseudocode
val currentSubroutine: KtElement
val returnSubroutine: KtElement
// Scopes
fun enterBlockScope(block: KtElement)
fun exitBlockScope(block: KtElement)
fun getSubroutineExitPoint(labelElement: KtElement): Label?
fun getLoopConditionEntryPoint(loop: KtLoopExpression): Label?
fun getLoopExitPoint(loop: KtLoopExpression): Label?
// Declarations
fun declareParameter(parameter: KtParameter)
fun declareVariable(property: KtVariableDeclaration)
fun declareFunction(subroutine: KtElement, pseudocode: Pseudocode)
fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, eventOccurrencesRange: EventOccurrencesRange)
fun declareEntryOrObject(entryOrObject: KtClassOrObject)
// Labels
fun createUnboundLabel(): Label
fun createUnboundLabel(name: String): Label
fun bindLabel(label: Label)
// Jumps
fun jump(label: Label, element: KtElement)
fun jumpOnFalse(label: Label, element: KtElement, conditionValue: PseudoValue?)
fun jumpOnTrue(label: Label, element: KtElement, conditionValue: PseudoValue?)
fun nondeterministicJump(label: Label, element: KtElement, inputValue: PseudoValue?) // Maybe, jump to label
fun nondeterministicJump(label: List<Label>, element: KtElement)
fun jumpToError(element: KtElement)
fun returnValue(returnExpression: KtExpression, returnValue: PseudoValue, subroutine: KtElement)
fun returnNoValue(returnExpression: KtReturnExpression, subroutine: KtElement)
fun throwException(throwExpression: KtThrowExpression, thrownValue: PseudoValue)
// Loops
fun enterLoop(expression: KtLoopExpression): LoopInfo
fun enterLoopBody(expression: KtLoopExpression)
fun exitLoopBody(expression: KtLoopExpression)
val currentLoop: KtLoopExpression?
// Try-Finally
fun enterTryFinally(trigger: GenerationTrigger)
fun exitTryFinally()
fun repeatPseudocode(startLabel: Label, finishLabel: Label)
// Reading values
fun mark(element: KtElement)
fun getBoundValue(element: KtElement?): PseudoValue?
fun bindValue(value: PseudoValue, element: KtElement)
fun newValue(element: KtElement?): PseudoValue
fun loadUnit(expression: KtExpression)
fun loadConstant(expression: KtExpression, constant: CompileTimeConstant<*>?): InstructionWithValue
fun createAnonymousObject(expression: KtObjectLiteralExpression): InstructionWithValue
fun createLambda(expression: KtFunction): InstructionWithValue
fun loadStringTemplate(expression: KtStringTemplateExpression, inputValues: List<PseudoValue>): InstructionWithValue
fun magic(
instructionElement: KtElement,
valueElement: KtElement?,
inputValues: List<PseudoValue>,
kind: MagicKind
): MagicInstruction
fun merge(
expression: KtExpression,
inputValues: List<PseudoValue>
): MergeInstruction
fun readVariable(
expression: KtExpression,
resolvedCall: ResolvedCall<*>,
receiverValues: Map<PseudoValue, ReceiverValue>
): ReadValueInstruction
fun call(
valueElement: KtElement,
resolvedCall: ResolvedCall<*>,
receiverValues: Map<PseudoValue, ReceiverValue>,
arguments: Map<PseudoValue, ValueParameterDescriptor>
): CallInstruction
enum class PredefinedOperation {
AND,
OR,
NOT_NULL_ASSERTION
}
fun predefinedOperation(
expression: KtExpression,
operation: PredefinedOperation,
inputValues: List<PseudoValue>
): OperationInstruction
fun read(element: KtElement, target: AccessTarget, receiverValues: Map<PseudoValue, ReceiverValue>): ReadValueInstruction
fun write(
assignment: KtElement,
lValue: KtElement,
rValue: PseudoValue,
target: AccessTarget,
receiverValues: Map<PseudoValue, ReceiverValue>
)
}
@@ -0,0 +1,219 @@
/*
* Copyright 2010-2015 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
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.EventOccurrencesRange
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
abstract class ControlFlowBuilderAdapter : ControlFlowBuilder {
protected abstract val delegateBuilder: ControlFlowBuilder
override fun loadUnit(expression: KtExpression) {
delegateBuilder.loadUnit(expression)
}
override fun loadConstant(expression: KtExpression, constant: CompileTimeConstant<*>?): InstructionWithValue =
delegateBuilder.loadConstant(expression, constant)
override fun createAnonymousObject(expression: KtObjectLiteralExpression): InstructionWithValue =
delegateBuilder.createAnonymousObject(expression)
override fun createLambda(expression: KtFunction): InstructionWithValue = delegateBuilder.createLambda(expression)
override fun loadStringTemplate(expression: KtStringTemplateExpression, inputValues: List<PseudoValue>): InstructionWithValue =
delegateBuilder.loadStringTemplate(expression, inputValues)
override fun magic(
instructionElement: KtElement,
valueElement: KtElement?,
inputValues: List<PseudoValue>,
kind: MagicKind
): MagicInstruction = delegateBuilder.magic(instructionElement, valueElement, inputValues, kind)
override fun merge(expression: KtExpression, inputValues: List<PseudoValue>): MergeInstruction =
delegateBuilder.merge(expression, inputValues)
override fun readVariable(
expression: KtExpression,
resolvedCall: ResolvedCall<*>,
receiverValues: Map<PseudoValue, ReceiverValue>
): ReadValueInstruction =
delegateBuilder.readVariable(expression, resolvedCall, receiverValues)
override fun call(
valueElement: KtElement,
resolvedCall: ResolvedCall<*>,
receiverValues: Map<PseudoValue, ReceiverValue>,
arguments: Map<PseudoValue, ValueParameterDescriptor>
): CallInstruction =
delegateBuilder.call(valueElement, resolvedCall, receiverValues, arguments)
override fun predefinedOperation(
expression: KtExpression,
operation: ControlFlowBuilder.PredefinedOperation,
inputValues: List<PseudoValue>
): OperationInstruction = delegateBuilder.predefinedOperation(expression, operation, inputValues)
override fun createUnboundLabel(): Label = delegateBuilder.createUnboundLabel()
override fun createUnboundLabel(name: String): Label = delegateBuilder.createUnboundLabel(name)
override fun bindLabel(label: Label) {
delegateBuilder.bindLabel(label)
}
override fun jump(label: Label, element: KtElement) {
delegateBuilder.jump(label, element)
}
override fun jumpOnFalse(label: Label, element: KtElement, conditionValue: PseudoValue?) {
delegateBuilder.jumpOnFalse(label, element, conditionValue)
}
override fun jumpOnTrue(label: Label, element: KtElement, conditionValue: PseudoValue?) {
delegateBuilder.jumpOnTrue(label, element, conditionValue)
}
override fun nondeterministicJump(label: Label, element: KtElement, inputValue: PseudoValue?) {
delegateBuilder.nondeterministicJump(label, element, inputValue)
}
override fun nondeterministicJump(label: List<Label>, element: KtElement) {
delegateBuilder.nondeterministicJump(label, element)
}
override fun jumpToError(element: KtElement) {
delegateBuilder.jumpToError(element)
}
override fun throwException(throwExpression: KtThrowExpression, thrownValue: PseudoValue) {
delegateBuilder.throwException(throwExpression, thrownValue)
}
override fun getSubroutineExitPoint(labelElement: KtElement): Label? = delegateBuilder.getSubroutineExitPoint(labelElement)
override fun getLoopConditionEntryPoint(loop: KtLoopExpression): Label? = delegateBuilder.getLoopConditionEntryPoint(loop)
override fun getLoopExitPoint(loop: KtLoopExpression): Label? = delegateBuilder.getLoopExitPoint(loop)
override fun enterLoop(expression: KtLoopExpression): LoopInfo = delegateBuilder.enterLoop(expression)
override fun enterLoopBody(expression: KtLoopExpression) {
delegateBuilder.enterLoopBody(expression)
}
override fun exitLoopBody(expression: KtLoopExpression) {
delegateBuilder.exitLoopBody(expression)
}
override val currentLoop: KtLoopExpression?
get() = delegateBuilder.currentLoop
override fun enterTryFinally(trigger: GenerationTrigger) {
delegateBuilder.enterTryFinally(trigger)
}
override fun exitTryFinally() {
delegateBuilder.exitTryFinally()
}
override fun enterSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange?) {
delegateBuilder.enterSubroutine(subroutine, eventOccurrencesRange)
}
override fun exitSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange?): Pseudocode =
delegateBuilder.exitSubroutine(subroutine, eventOccurrencesRange)
override val currentSubroutine: KtElement
get() = delegateBuilder.currentSubroutine
override val returnSubroutine: KtElement
get() = delegateBuilder.returnSubroutine
override fun returnValue(returnExpression: KtExpression, returnValue: PseudoValue, subroutine: KtElement) {
delegateBuilder.returnValue(returnExpression, returnValue, subroutine)
}
override fun returnNoValue(returnExpression: KtReturnExpression, subroutine: KtElement) {
delegateBuilder.returnNoValue(returnExpression, subroutine)
}
override fun read(element: KtElement, target: AccessTarget, receiverValues: Map<PseudoValue, ReceiverValue>) =
delegateBuilder.read(element, target, receiverValues)
override fun write(
assignment: KtElement,
lValue: KtElement,
rValue: PseudoValue,
target: AccessTarget,
receiverValues: Map<PseudoValue, ReceiverValue>
) {
delegateBuilder.write(assignment, lValue, rValue, target, receiverValues)
}
override fun declareParameter(parameter: KtParameter) {
delegateBuilder.declareParameter(parameter)
}
override fun declareVariable(property: KtVariableDeclaration) {
delegateBuilder.declareVariable(property)
}
override fun declareFunction(subroutine: KtElement, pseudocode: Pseudocode) {
delegateBuilder.declareFunction(subroutine, pseudocode)
}
override fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, eventOccurrencesRange: EventOccurrencesRange) {
delegateBuilder.declareInlinedFunction(subroutine, pseudocode, eventOccurrencesRange)
}
override fun declareEntryOrObject(entryOrObject: KtClassOrObject) {
delegateBuilder.declareEntryOrObject(entryOrObject)
}
override fun repeatPseudocode(startLabel: Label, finishLabel: Label) {
delegateBuilder.repeatPseudocode(startLabel, finishLabel)
}
override fun mark(element: KtElement) {
delegateBuilder.mark(element)
}
override fun getBoundValue(element: KtElement?): PseudoValue? = delegateBuilder.getBoundValue(element)
override fun bindValue(value: PseudoValue, element: KtElement) {
delegateBuilder.bindValue(value, element)
}
override fun newValue(element: KtElement?): PseudoValue = delegateBuilder.newValue(element)
override fun enterBlockScope(block: KtElement) {
delegateBuilder.enterBlockScope(block)
}
override fun exitBlockScope(block: KtElement) {
delegateBuilder.exitBlockScope(block)
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.util.javaslang.ImmutableHashMap
import org.jetbrains.kotlin.util.javaslang.ImmutableMap
interface ReadOnlyControlFlowInfo<K : Any, D : Any> {
fun getOrNull(key: K): D?
// Only used in tests
fun asMap(): ImmutableMap<K, D>
}
abstract class ControlFlowInfo<S : ControlFlowInfo<S, K, D>, K : Any, D : Any>
internal constructor(
protected val map: ImmutableMap<K, D> = ImmutableHashMap.empty()
) : ImmutableMap<K, D> by map, ReadOnlyControlFlowInfo<K, D> {
protected abstract fun copy(newMap: ImmutableMap<K, D>): S
override fun put(key: K, value: D): S = put(key, value, this[key].getOrElse(null as D?))
/**
* This overload exists just for sake of optimizations: in some cases we've just retrieved the old value,
* so we don't need to scan through the persistent hashmap again
*/
fun put(key: K, value: D, oldValue: D?): S {
@Suppress("UNCHECKED_CAST")
// Avoid a copy instance creation if new value is the same
if (value == oldValue) return this as S
return copy(map.put(key, value))
}
override fun getOrNull(key: K): D? = this[key].getOrElse(null as D?)
override fun asMap() = this
fun retainAll(predicate: (K) -> Boolean): S = copy(map.removeAll(map.keySet().filterNot(predicate)))
override fun equals(other: Any?) = map == (other as? ControlFlowInfo<*, *, *>)?.map
override fun hashCode() = map.hashCode()
override fun toString() = map.toString()
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2015 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;
public interface GenerationTrigger {
void generate();
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
interface Label {
val pseudocode: Pseudocode
val name: String
val targetInstructionIndex: Int
fun resolveToInstruction(): Instruction
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.psi.KtLoopExpression
class LoopInfo(
override val element: KtLoopExpression,
entryPoint: Label,
exitPoint: Label,
val bodyEntryPoint: Label,
val bodyExitPoint: Label,
val conditionEntryPoint: Label
) : BreakableBlockInfo(element, entryPoint, exitPoint) {
init {
markReferablePoints(bodyEntryPoint, bodyExitPoint, conditionEntryPoint)
}
}
@@ -0,0 +1,219 @@
/*
* Copyright 2010-2015 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.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
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD
import java.util.*
fun Pseudocode.traverse(
traversalOrder: TraversalOrder,
analyzeInstruction: (Instruction) -> Unit
) {
val instructions = getInstructions(traversalOrder)
for (instruction in instructions) {
if (instruction is LocalFunctionDeclarationInstruction) {
instruction.body.traverse(traversalOrder, analyzeInstruction)
}
analyzeInstruction(instruction)
}
}
fun <D> Pseudocode.traverse(
traversalOrder: TraversalOrder,
edgesMap: Map<Instruction, Edges<D>>,
analyzeInstruction: (Instruction, D, D) -> Unit
) {
val instructions = getInstructions(traversalOrder)
for (instruction in instructions) {
if (instruction is LocalFunctionDeclarationInstruction) {
instruction.body.traverse(traversalOrder, edgesMap, analyzeInstruction)
}
val edges = edgesMap[instruction] ?: continue
analyzeInstruction(instruction, edges.incoming, edges.outgoing)
}
}
fun <I : ControlFlowInfo<*, *, *>> Pseudocode.collectData(
traversalOrder: TraversalOrder,
mergeEdges: (Instruction, Collection<I>) -> Edges<I>,
updateEdge: (Instruction, Instruction, I) -> I,
initialInfo: I
): Map<Instruction, Edges<I>> {
val edgesMap = LinkedHashMap<Instruction, Edges<I>>()
val startInstruction = getStartInstruction(traversalOrder)
edgesMap[startInstruction] = Edges(initialInfo, initialInfo)
val changed = mutableMapOf<Instruction, Boolean>()
do {
collectDataFromSubgraph(
traversalOrder, edgesMap,
mergeEdges, updateEdge, Collections.emptyList<Instruction>(), changed, false
)
} while (changed.any { it.value })
return edgesMap
}
private fun <I : ControlFlowInfo<*, *, *>> Pseudocode.collectDataFromSubgraph(
traversalOrder: TraversalOrder,
edgesMap: MutableMap<Instruction, Edges<I>>,
mergeEdges: (Instruction, Collection<I>) -> Edges<I>,
updateEdge: (Instruction, Instruction, I) -> I,
previousSubGraphInstructions: Collection<Instruction>,
changed: MutableMap<Instruction, Boolean>,
isLocal: Boolean
) {
val instructions = getInstructions(traversalOrder)
val startInstruction = getStartInstruction(traversalOrder)
for (instruction in instructions) {
val isStart = instruction.isStartInstruction(traversalOrder)
if (!isLocal && isStart)
continue
val previousInstructions =
getPreviousIncludingSubGraphInstructions(instruction, traversalOrder, startInstruction, previousSubGraphInstructions)
if (instruction is LocalFunctionDeclarationInstruction) {
val subroutinePseudocode = instruction.body
subroutinePseudocode.collectDataFromSubgraph(
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 {
Edges(updateEdge(lastInstruction, instruction, it.incoming), updateEdge(lastInstruction, instruction, it.outgoing))
}
updateEdgeDataForInstruction(instruction, previousValue, updatedValue, edgesMap, changed)
continue
}
val previousDataValue = edgesMap[instruction]
if (previousDataValue != null && previousInstructions.all { changed[it] == false }) {
changed[instruction] = false
continue
}
val incomingEdgesData = HashSet<I>()
for (previousInstruction in previousInstructions) {
val previousData = edgesMap[previousInstruction] ?: continue
incomingEdgesData.add(updateEdge(previousInstruction, instruction, previousData.outgoing))
}
val mergedData = mergeEdges(instruction, incomingEdgesData)
updateEdgeDataForInstruction(instruction, previousDataValue, mergedData, edgesMap, changed)
}
}
private fun getPreviousIncludingSubGraphInstructions(
instruction: Instruction,
traversalOrder: TraversalOrder,
startInstruction: Instruction,
previousSubGraphInstructions: Collection<Instruction>
): Collection<Instruction> {
val previous = instruction.getPreviousInstructions(traversalOrder)
if (instruction != startInstruction || previousSubGraphInstructions.isEmpty()) {
return previous
}
val result = ArrayList(previous)
result.addAll(previousSubGraphInstructions)
return result
}
private fun <I : ControlFlowInfo<*, *, *>> updateEdgeDataForInstruction(
instruction: Instruction,
previousValue: Edges<I>?,
newValue: Edges<I>?,
edgesMap: MutableMap<Instruction, Edges<I>>,
changed: MutableMap<Instruction, Boolean>
) {
if (previousValue != newValue && newValue != null) {
changed[instruction] = true
edgesMap[instruction] = newValue
} else {
changed[instruction] = false
}
}
data class Edges<out T>(val incoming: T, val outgoing: T)
enum class TraverseInstructionResult {
CONTINUE,
SKIP,
HALT
}
// returns false when interrupted by handler
fun traverseFollowingInstructions(
rootInstruction: Instruction,
visited: MutableSet<Instruction> = HashSet(),
order: TraversalOrder = FORWARD,
// true to continue traversal
handler: ((Instruction) -> TraverseInstructionResult)?
): Boolean {
val stack = ArrayDeque<Instruction>()
stack.push(rootInstruction)
while (!stack.isEmpty()) {
val instruction = stack.pop()
if (!visited.add(instruction)) continue
when (handler?.let { it(instruction) } ?: TraverseInstructionResult.CONTINUE) {
TraverseInstructionResult.CONTINUE -> instruction.getNextInstructions(order).forEach { stack.push(it) }
TraverseInstructionResult.SKIP -> {
}
TraverseInstructionResult.HALT -> return false
}
}
return true
}
enum class TraversalOrder {
FORWARD,
BACKWARD
}
fun Pseudocode.getStartInstruction(traversalOrder: TraversalOrder): Instruction =
if (traversalOrder == FORWARD) enterInstruction else sinkInstruction
fun Pseudocode.getLastInstruction(traversalOrder: TraversalOrder): Instruction =
if (traversalOrder == FORWARD) sinkInstruction else enterInstruction
fun Pseudocode.getInstructions(traversalOrder: TraversalOrder): List<Instruction> =
if (traversalOrder == FORWARD) instructions else reversedInstructions
fun Instruction.getNextInstructions(traversalOrder: TraversalOrder): Collection<Instruction> =
if (traversalOrder == FORWARD) nextInstructions else previousInstructions
fun Instruction.getPreviousInstructions(traversalOrder: TraversalOrder): Collection<Instruction> =
if (traversalOrder == FORWARD) previousInstructions else nextInstructions
fun Instruction.isStartInstruction(traversalOrder: TraversalOrder): Boolean =
if (traversalOrder == FORWARD) this is SubroutineEnterInstruction else this is SubroutineSinkInstruction
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2016 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
import org.jetbrains.kotlin.psi.KtElement
class SubroutineInfo(subroutine: KtElement, entryPoint: Label, exitPoint: Label) : BreakableBlockInfo(subroutine, entryPoint, exitPoint)
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2015 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;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction;
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.ThrowExceptionInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.MarkInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineExitInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.SubroutineSinkInstruction;
import org.jetbrains.kotlin.psi.KtElement;
/**
* Returns true when visited instruction may lie on a path from a tail-call-like operation to the sink of the subroutine
*/
public class TailInstructionDetector extends InstructionVisitorWithResult<Boolean> {
private final KtElement subroutine;
public TailInstructionDetector(@NotNull KtElement subroutine) {
this.subroutine = subroutine;
}
@Override
public Boolean visitInstruction(@NotNull Instruction instruction) {
return false;
}
@Override
public Boolean visitSubroutineExit(@NotNull SubroutineExitInstruction instruction) {
return !instruction.isError() && instruction.getSubroutine() == subroutine;
}
@Override
public Boolean visitSubroutineSink(@NotNull SubroutineSinkInstruction instruction) {
return instruction.getSubroutine() == subroutine;
}
@Override
public Boolean visitJump(@NotNull AbstractJumpInstruction instruction) {
return true;
}
@Override
public Boolean visitThrowExceptionInstruction(@NotNull ThrowExceptionInstruction instruction) {
return false;
}
@Override
public Boolean visitMarkInstruction(@NotNull MarkInstruction instruction) {
return true;
}
@Override
public Boolean visitMagic(@NotNull MagicInstruction instruction) {
return instruction.getSynthetic();
}
@Override
public Boolean visitMerge(@NotNull MergeInstruction instruction) {
return true;
}
}
@@ -0,0 +1,433 @@
/*
* Copyright 2010-2015 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
import com.intellij.util.containers.Stack
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cfg.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
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.EventOccurrencesRange
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import java.util.*
class ControlFlowInstructionsGenerator : ControlFlowBuilderAdapter() {
private var builder: ControlFlowBuilder? = null
override val delegateBuilder: ControlFlowBuilder
get() = builder ?: throw AssertionError("Builder stack is empty in ControlFlowInstructionsGenerator!")
private val loopInfo = Stack<LoopInfo>()
private val blockScopes = Stack<BlockScope>()
private val elementToLoopInfo = HashMap<KtLoopExpression, LoopInfo>()
private val elementToSubroutineInfo = HashMap<KtElement, SubroutineInfo>()
private var labelCount = 0
private val builders = Stack<ControlFlowInstructionsGeneratorWorker>()
private val allBlocks = Stack<BlockInfo>()
private fun pushBuilder(scopingElement: KtElement, subroutine: KtElement, shouldInline: Boolean) {
val worker = ControlFlowInstructionsGeneratorWorker(scopingElement, subroutine, shouldInline)
builders.push(worker)
builder = worker
}
private fun popBuilder(): ControlFlowInstructionsGeneratorWorker {
val worker = builders.pop()
builder = if (!builders.isEmpty()) {
builders.peek()
} else {
null
}
return worker
}
override fun enterSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange?) {
val builder = builder
val shouldInlnie = eventOccurrencesRange != null
if (builder != null && subroutine is KtFunctionLiteral) {
pushBuilder(subroutine, builder.returnSubroutine, shouldInlnie)
} else {
pushBuilder(subroutine, subroutine, shouldInlnie)
}
delegateBuilder.enterBlockScope(subroutine)
delegateBuilder.enterSubroutine(subroutine)
}
override fun exitSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange?): Pseudocode {
super.exitSubroutine(subroutine, eventOccurrencesRange)
delegateBuilder.exitBlockScope(subroutine)
val worker = popBuilder()
if (!builders.empty()) {
val builder = builders.peek()
if (eventOccurrencesRange == null) {
builder.declareFunction(subroutine, worker.pseudocode)
} else {
builder.declareInlinedFunction(subroutine, worker.pseudocode, eventOccurrencesRange)
}
}
return worker.pseudocode
}
private inner class ControlFlowInstructionsGeneratorWorker(
scopingElement: KtElement,
override val returnSubroutine: KtElement,
shouldInline: Boolean
) : ControlFlowBuilder {
val pseudocode: PseudocodeImpl = PseudocodeImpl(scopingElement, shouldInline)
private val error: Label = pseudocode.createLabel("error", null)
private val sink: Label = pseudocode.createLabel("sink", null)
private val valueFactory = object : PseudoValueFactoryImpl() {
override fun newValue(element: KtElement?, instruction: InstructionWithValue?): PseudoValue {
val value = super.newValue(element, instruction)
if (element != null) {
bindValue(value, element)
}
return value
}
}
private fun add(instruction: Instruction) {
pseudocode.addInstruction(instruction)
}
override fun createUnboundLabel(): Label = pseudocode.createLabel("L" + labelCount++, null)
override fun createUnboundLabel(name: String): Label = pseudocode.createLabel("L" + labelCount++, name)
override fun enterLoop(expression: KtLoopExpression): LoopInfo {
if (expression is KtDoWhileExpression) {
(pseudocode.rootPseudocode as PseudocodeImpl).containsDoWhile = true
}
val info = LoopInfo(
expression,
createUnboundLabel("loop entry point"),
createUnboundLabel("loop exit point"),
createUnboundLabel("body entry point"),
createUnboundLabel("body exit point"),
createUnboundLabel("condition entry point")
)
bindLabel(info.entryPoint)
elementToLoopInfo.put(expression, info)
return info
}
override fun enterLoopBody(expression: KtLoopExpression) {
val info = elementToLoopInfo[expression]!!
bindLabel(info.bodyEntryPoint)
loopInfo.push(info)
allBlocks.push(info)
}
override fun exitLoopBody(expression: KtLoopExpression) {
val info = loopInfo.pop()
elementToLoopInfo.remove(expression)
allBlocks.pop()
bindLabel(info.bodyExitPoint)
}
override val currentLoop: KtLoopExpression?
get() = if (loopInfo.empty()) null else loopInfo.peek().element
override fun enterSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange?) {
val blockInfo = SubroutineInfo(
subroutine,
/* entry point */ createUnboundLabel(),
/* exit point */ createUnboundLabel()
)
elementToSubroutineInfo.put(subroutine, blockInfo)
allBlocks.push(blockInfo)
bindLabel(blockInfo.entryPoint)
add(SubroutineEnterInstruction(subroutine, currentScope))
}
override val currentSubroutine: KtElement
get() = pseudocode.correspondingElement
override fun getLoopConditionEntryPoint(loop: KtLoopExpression): Label? = elementToLoopInfo[loop]?.conditionEntryPoint
override fun getLoopExitPoint(loop: KtLoopExpression): Label? =// It's quite possible to have null here, see testBreakInsideLocal
elementToLoopInfo[loop]?.exitPoint
override fun getSubroutineExitPoint(labelElement: KtElement): Label? =
// It's quite possible to have null here, e.g. for non-local returns (see KT-10823)
elementToSubroutineInfo[labelElement]?.exitPoint
private val currentScope: BlockScope
get() = blockScopes.peek()
override fun enterBlockScope(block: KtElement) {
val current = if (blockScopes.isEmpty()) null else currentScope
val scope = BlockScope(current, block)
blockScopes.push(scope)
}
override fun exitBlockScope(block: KtElement) {
val currentScope = currentScope
assert(currentScope.block === block) {
"Exit from not the current block scope.\n" +
"Current scope is for a block: " + currentScope.block.text + ".\n" +
"Exit from the scope for: " + block.text
}
blockScopes.pop()
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private fun handleJumpInsideTryFinally(jumpTarget: Label) {
val finallyBlocks = ArrayList<TryFinallyBlockInfo>()
for (blockInfo in allBlocks.asReversed()) {
when (blockInfo) {
is BreakableBlockInfo -> if (blockInfo.referablePoints.contains(jumpTarget) || jumpTarget === error) {
for (finallyBlockInfo in finallyBlocks) {
finallyBlockInfo.generateFinallyBlock()
}
return
}
is TryFinallyBlockInfo -> finallyBlocks.add(blockInfo)
}
}
}
override fun exitSubroutine(subroutine: KtElement, eventOccurrencesRange: EventOccurrencesRange?): Pseudocode {
getSubroutineExitPoint(subroutine)?.let { bindLabel(it) }
pseudocode.addExitInstruction(SubroutineExitInstruction(subroutine, currentScope, false))
bindLabel(error)
pseudocode.addErrorInstruction(SubroutineExitInstruction(subroutine, currentScope, true))
bindLabel(sink)
pseudocode.addSinkInstruction(SubroutineSinkInstruction(subroutine, currentScope, "<SINK>"))
elementToSubroutineInfo.remove(subroutine)
allBlocks.pop()
return pseudocode
}
override fun mark(element: KtElement) {
add(MarkInstruction(element, currentScope))
}
override fun getBoundValue(element: KtElement?): PseudoValue? = pseudocode.getElementValue(element)
override fun bindValue(value: PseudoValue, element: KtElement) {
pseudocode.bindElementToValue(element, value)
}
override fun newValue(element: KtElement?): PseudoValue = valueFactory.newValue(element, null)
override fun returnValue(returnExpression: KtExpression, returnValue: PseudoValue, subroutine: KtElement) {
val exitPoint = getSubroutineExitPoint(subroutine) ?: return
handleJumpInsideTryFinally(exitPoint)
add(ReturnValueInstruction(returnExpression, currentScope, exitPoint, returnValue, subroutine))
}
override fun returnNoValue(returnExpression: KtReturnExpression, subroutine: KtElement) {
val exitPoint = getSubroutineExitPoint(subroutine) ?: return
handleJumpInsideTryFinally(exitPoint)
add(ReturnNoValueInstruction(returnExpression, currentScope, exitPoint, subroutine))
}
override fun write(
assignment: KtElement,
lValue: KtElement,
rValue: PseudoValue,
target: AccessTarget,
receiverValues: Map<PseudoValue, ReceiverValue>
) {
add(WriteValueInstruction(assignment, currentScope, target, receiverValues, lValue, rValue))
}
override fun declareParameter(parameter: KtParameter) {
add(VariableDeclarationInstruction(parameter, currentScope))
}
override fun declareVariable(property: KtVariableDeclaration) {
add(VariableDeclarationInstruction(property, currentScope))
}
override fun declareFunction(subroutine: KtElement, pseudocode: Pseudocode) {
add(LocalFunctionDeclarationInstruction(subroutine, pseudocode, currentScope))
}
override fun declareInlinedFunction(subroutine: KtElement, pseudocode: Pseudocode, eventOccurrencesRange: EventOccurrencesRange) {
add(InlinedLocalFunctionDeclarationInstruction(subroutine, pseudocode, currentScope, eventOccurrencesRange))
}
override fun declareEntryOrObject(entryOrObject: KtClassOrObject) {
add(VariableDeclarationInstruction(entryOrObject, currentScope))
}
override fun loadUnit(expression: KtExpression) {
add(LoadUnitValueInstruction(expression, currentScope))
}
override fun jump(label: Label, element: KtElement) {
handleJumpInsideTryFinally(label)
add(UnconditionalJumpInstruction(element, label, currentScope))
}
override fun jumpOnFalse(label: Label, element: KtElement, conditionValue: PseudoValue?) {
handleJumpInsideTryFinally(label)
add(ConditionalJumpInstruction(element, false, currentScope, label, conditionValue))
}
override fun jumpOnTrue(label: Label, element: KtElement, conditionValue: PseudoValue?) {
handleJumpInsideTryFinally(label)
add(ConditionalJumpInstruction(element, true, currentScope, label, conditionValue))
}
override fun bindLabel(label: Label) {
pseudocode.bindLabel(label as PseudocodeLabel)
}
override fun nondeterministicJump(label: Label, element: KtElement, inputValue: PseudoValue?) {
handleJumpInsideTryFinally(label)
add(NondeterministicJumpInstruction(element, listOf(label), currentScope, inputValue))
}
override fun nondeterministicJump(label: List<Label>, element: KtElement) {
//todo
//handleJumpInsideTryFinally(label);
add(NondeterministicJumpInstruction(element, label, currentScope, null))
}
override fun jumpToError(element: KtElement) {
handleJumpInsideTryFinally(error)
add(UnconditionalJumpInstruction(element, error, currentScope))
}
override fun enterTryFinally(trigger: GenerationTrigger) {
allBlocks.push(TryFinallyBlockInfo(trigger))
}
override fun throwException(throwExpression: KtThrowExpression, thrownValue: PseudoValue) {
handleJumpInsideTryFinally(error)
add(ThrowExceptionInstruction(throwExpression, currentScope, error, thrownValue))
}
override fun exitTryFinally() {
val pop = allBlocks.pop()
assert(pop is TryFinallyBlockInfo)
}
override fun repeatPseudocode(startLabel: Label, finishLabel: Label) {
labelCount = pseudocode.repeatPart(startLabel, finishLabel, labelCount)
}
override fun loadConstant(expression: KtExpression, constant: CompileTimeConstant<*>?) = read(expression)
override fun createAnonymousObject(expression: KtObjectLiteralExpression) = read(expression)
override fun createLambda(expression: KtFunction) =
read(if (expression is KtFunctionLiteral) expression.getParent() as KtLambdaExpression else expression)
override fun loadStringTemplate(
expression: KtStringTemplateExpression,
inputValues: List<PseudoValue>
): InstructionWithValue =
if (inputValues.isEmpty()) read(expression)
else magic(expression, expression, inputValues, MagicKind.STRING_TEMPLATE)
override fun magic(
instructionElement: KtElement,
valueElement: KtElement?,
inputValues: List<PseudoValue>,
kind: MagicKind
): MagicInstruction {
val instruction = MagicInstruction(
instructionElement, valueElement, currentScope, inputValues, kind, valueFactory
)
add(instruction)
return instruction
}
override fun merge(expression: KtExpression, inputValues: List<PseudoValue>): MergeInstruction {
val instruction = MergeInstruction(expression, currentScope, inputValues, valueFactory)
add(instruction)
return instruction
}
override fun readVariable(
expression: KtExpression,
resolvedCall: ResolvedCall<*>,
receiverValues: Map<PseudoValue, ReceiverValue>
) = read(expression, resolvedCall, receiverValues)
override fun call(
valueElement: KtElement,
resolvedCall: ResolvedCall<*>,
receiverValues: Map<PseudoValue, ReceiverValue>,
arguments: Map<PseudoValue, ValueParameterDescriptor>
): CallInstruction {
val returnType = resolvedCall.resultingDescriptor.returnType
val instruction = CallInstruction(
valueElement,
currentScope,
resolvedCall,
receiverValues,
arguments,
if (returnType != null && KotlinBuiltIns.isNothing(returnType)) null else valueFactory
)
add(instruction)
return instruction
}
override fun predefinedOperation(
expression: KtExpression,
operation: ControlFlowBuilder.PredefinedOperation,
inputValues: List<PseudoValue>
): OperationInstruction = magic(expression, expression, inputValues, getMagicKind(operation))
private fun getMagicKind(operation: ControlFlowBuilder.PredefinedOperation) = when (operation) {
ControlFlowBuilder.PredefinedOperation.AND -> MagicKind.AND
ControlFlowBuilder.PredefinedOperation.OR -> MagicKind.OR
ControlFlowBuilder.PredefinedOperation.NOT_NULL_ASSERTION -> MagicKind.NOT_NULL_ASSERTION
}
override fun read(
element: KtElement,
target: AccessTarget,
receiverValues: Map<PseudoValue, ReceiverValue>
) = ReadValueInstruction(element, currentScope, target, receiverValues, valueFactory).apply {
add(this)
}
private fun read(
expression: KtExpression,
resolvedCall: ResolvedCall<*>? = null,
receiverValues: Map<PseudoValue, ReceiverValue> = emptyMap()
) = read(expression, if (resolvedCall != null) AccessTarget.Call(resolvedCall) else AccessTarget.BlackBox, receiverValues)
}
private class TryFinallyBlockInfo(private val finallyBlock: GenerationTrigger) : BlockInfo() {
fun generateFinallyBlock() {
finallyBlock.generate()
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue
interface PseudoValue {
val debugName: String
val element: KtElement?
val createdAt: InstructionWithValue?
}
interface PseudoValueFactory {
fun newValue(element: KtElement?, instruction: InstructionWithValue?): PseudoValue
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.InstructionWithValue
class PseudoValueImpl(
override val debugName: String,
override val element: KtElement?,
override val createdAt: InstructionWithValue?
) : PseudoValue {
override fun toString(): String = debugName
}
open class PseudoValueFactoryImpl : PseudoValueFactory {
private var lastIndex: Int = 0
override fun newValue(element: KtElement?, instruction: InstructionWithValue?): PseudoValue {
return PseudoValueImpl((instruction?.let { "" } ?: "!") + "<v${lastIndex++}>", element, instruction)
}
}
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstruction
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.psi.KtElement
interface Pseudocode {
val correspondingElement: KtElement
val parent: Pseudocode?
val localDeclarations: Set<LocalFunctionDeclarationInstruction>
val instructions: List<Instruction>
val reversedInstructions: List<Instruction>
val instructionsIncludingDeadCode: List<Instruction>
val exitInstruction: SubroutineExitInstruction
val errorInstruction: SubroutineExitInstruction
val sinkInstruction: SubroutineSinkInstruction
val enterInstruction: SubroutineEnterInstruction
val isInlined: Boolean
val containsDoWhile: Boolean
val rootPseudocode: Pseudocode
fun getElementValue(element: KtElement?): PseudoValue?
fun getValueElements(value: PseudoValue?): List<KtElement>
fun getUsages(value: PseudoValue?): List<Instruction>
fun isSideEffectFree(instruction: Instruction): Boolean
fun copy(): Pseudocode
fun instructionForElement(element: KtElement): KtElementInstruction?
}
@@ -0,0 +1,453 @@
/*
* Copyright 2010-2015 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
import com.google.common.collect.HashMultimap
import com.google.common.collect.Multimap
import com.intellij.util.containers.BidirectionalMap
import org.jetbrains.kotlin.cfg.Label
import org.jetbrains.kotlin.cfg.pseudocode.instructions.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind
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.*
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.BACKWARD
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder.FORWARD
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraverseInstructionResult
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverseFollowingInstructions
import org.jetbrains.kotlin.psi.KtElement
import java.util.*
class PseudocodeImpl(override val correspondingElement: KtElement, override val isInlined: Boolean) : Pseudocode {
internal val mutableInstructionList = ArrayList<Instruction>()
override val instructions = ArrayList<Instruction>()
private val elementsToValues = BidirectionalMap<KtElement, PseudoValue>()
private val valueUsages = hashMapOf<PseudoValue, MutableList<Instruction>>()
private val mergedValues = hashMapOf<PseudoValue, Set<PseudoValue>>()
private val sideEffectFree = hashSetOf<Instruction>()
override var parent: Pseudocode? = null
private set
override val localDeclarations: Set<LocalFunctionDeclarationInstruction> by lazy {
getLocalDeclarations(this)
}
val reachableInstructions = hashSetOf<Instruction>()
private val representativeInstructions = HashMap<KtElement, KtElementInstruction>()
private val labels = ArrayList<PseudocodeLabel>()
private var internalExitInstruction: SubroutineExitInstruction? = null
override val exitInstruction: SubroutineExitInstruction
get() = internalExitInstruction ?: throw AssertionError("Exit instruction is read before initialization")
private var internalSinkInstruction: SubroutineSinkInstruction? = null
override val sinkInstruction: SubroutineSinkInstruction
get() = internalSinkInstruction ?: throw AssertionError("Sink instruction is read before initialization")
private var internalErrorInstruction: SubroutineExitInstruction? = null
override val errorInstruction: SubroutineExitInstruction
get() = internalErrorInstruction ?: throw AssertionError("Error instruction is read before initialization")
private var postPrecessed = false
override var containsDoWhile: Boolean = false
internal set
private fun getLocalDeclarations(pseudocode: Pseudocode): Set<LocalFunctionDeclarationInstruction> {
val localDeclarations = linkedSetOf<LocalFunctionDeclarationInstruction>()
for (instruction in (pseudocode as PseudocodeImpl).mutableInstructionList) {
if (instruction is LocalFunctionDeclarationInstruction) {
localDeclarations.add(instruction)
localDeclarations.addAll(getLocalDeclarations(instruction.body))
}
}
return localDeclarations
}
override val rootPseudocode: Pseudocode
get() {
var parent = parent
while (parent != null) {
if (parent.parent == null) return parent
parent = parent.parent
}
return this
}
fun createLabel(name: String, comment: String?): PseudocodeLabel {
val label = PseudocodeLabel(this, name, comment)
labels.add(label)
return label
}
override val reversedInstructions: List<Instruction>
get() {
val traversedInstructions = linkedSetOf<Instruction>()
traverseFollowingInstructions(
if (this.isInlined) instructions.last() else sinkInstruction,
traversedInstructions,
BACKWARD,
null
)
if (traversedInstructions.size < instructions.size) {
val simplyReversedInstructions = instructions.reversed()
for (instruction in simplyReversedInstructions) {
if (!traversedInstructions.contains(instruction)) {
traverseFollowingInstructions(instruction, traversedInstructions, BACKWARD, null)
}
}
}
return traversedInstructions.toList()
}
override val instructionsIncludingDeadCode: List<Instruction>
get() = mutableInstructionList
//for tests only
fun getLabels(): List<PseudocodeLabel> = labels
fun addExitInstruction(exitInstruction: SubroutineExitInstruction) {
addInstruction(exitInstruction)
assert(internalExitInstruction == null) {
"Repeated initialization of exit instruction: $internalExitInstruction --> $exitInstruction"
}
internalExitInstruction = exitInstruction
}
fun addSinkInstruction(sinkInstruction: SubroutineSinkInstruction) {
addInstruction(sinkInstruction)
assert(internalSinkInstruction == null) {
"Repeated initialization of sink instruction: $internalSinkInstruction --> $sinkInstruction"
}
internalSinkInstruction = sinkInstruction
}
fun addErrorInstruction(errorInstruction: SubroutineExitInstruction) {
addInstruction(errorInstruction)
assert(internalErrorInstruction == null) {
"Repeated initialization of error instruction: $internalErrorInstruction --> $errorInstruction"
}
internalErrorInstruction = errorInstruction
}
fun addInstruction(instruction: Instruction) {
mutableInstructionList.add(instruction)
instruction.owner = this
if (instruction is KtElementInstruction) {
if (!representativeInstructions.containsKey(instruction.element)) {
representativeInstructions.put(instruction.element, instruction)
}
}
if (instruction is MergeInstruction) {
addMergedValues(instruction)
}
for (inputValue in instruction.inputValues) {
addValueUsage(inputValue, instruction)
for (mergedValue in getMergedValues(inputValue)) {
addValueUsage(mergedValue, instruction)
}
}
if (instruction.calcSideEffectFree()) {
sideEffectFree.add(instruction)
}
}
override val enterInstruction: SubroutineEnterInstruction
get() = mutableInstructionList[0] as SubroutineEnterInstruction
override fun getElementValue(element: KtElement?) = elementsToValues[element]
override fun getValueElements(value: PseudoValue?): List<KtElement> = elementsToValues.getKeysByValue(value) ?: emptyList()
override fun getUsages(value: PseudoValue?) = valueUsages[value] ?: mutableListOf()
override fun isSideEffectFree(instruction: Instruction) = sideEffectFree.contains(instruction)
fun bindElementToValue(element: KtElement, value: PseudoValue) {
elementsToValues.put(element, value)
}
fun bindLabel(label: PseudocodeLabel) {
assert(this == label.pseudocode) {
"Attempt to bind label $label to instruction from different pseudocode: " +
"\nowner pseudocode = ${label.pseudocode.mutableInstructionList}, " +
"\nbound pseudocode = ${this.mutableInstructionList}"
}
label.targetInstructionIndex = mutableInstructionList.size
}
private fun getMergedValues(value: PseudoValue) = mergedValues[value] ?: emptySet()
private fun addMergedValues(instruction: MergeInstruction) {
val result = LinkedHashSet<PseudoValue>()
for (value in instruction.inputValues) {
result.addAll(getMergedValues(value))
result.add(value)
}
mergedValues.put(instruction.outputValue, result)
}
private fun addValueUsage(value: PseudoValue, usage: Instruction) {
if (usage is MergeInstruction) return
valueUsages.getOrPut(
value
) { arrayListOf() }.add(usage)
}
fun postProcess() {
if (postPrecessed) return
postPrecessed = true
errorInstruction.sink = sinkInstruction
exitInstruction.sink = sinkInstruction
for ((index, instruction) in mutableInstructionList.withIndex()) {
//recursively invokes 'postProcess' for local declarations, thus it needs global set of reachable instructions
instruction.processInstruction(index)
}
collectAndCacheReachableInstructions()
}
private fun collectAndCacheReachableInstructions() {
collectReachableInstructions()
for (instruction in mutableInstructionList) {
if (reachableInstructions.contains(instruction)) {
instructions.add(instruction)
}
}
markDeadInstructions()
}
private fun Instruction.processInstruction(currentPosition: Int) {
accept(object : InstructionVisitor() {
override fun visitInstructionWithNext(instruction: InstructionWithNext) {
instruction.next = getNextPosition(currentPosition)
}
override fun visitJump(instruction: AbstractJumpInstruction) {
instruction.resolvedTarget = getJumpTarget(instruction.targetLabel)
}
override fun visitNondeterministicJump(instruction: NondeterministicJumpInstruction) {
instruction.next = getNextPosition(currentPosition)
val targetLabels = instruction.targetLabels
for (targetLabel in targetLabels) {
instruction.setResolvedTarget(targetLabel, getJumpTarget(targetLabel))
}
}
override fun visitConditionalJump(instruction: ConditionalJumpInstruction) {
val nextInstruction = getNextPosition(currentPosition)
val jumpTarget = getJumpTarget(instruction.targetLabel)
if (instruction.onTrue) {
instruction.nextOnFalse = nextInstruction
instruction.nextOnTrue = jumpTarget
} else {
instruction.nextOnFalse = jumpTarget
instruction.nextOnTrue = nextInstruction
}
visitJump(instruction)
}
override fun visitLocalFunctionDeclarationInstruction(instruction: LocalFunctionDeclarationInstruction) {
val body = instruction.body as PseudocodeImpl
body.parent = this@PseudocodeImpl
body.postProcess()
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
}
override fun visitSubroutineSink(instruction: SubroutineSinkInstruction) {
// Nothing
}
override fun visitInstruction(instruction: Instruction) {
throw UnsupportedOperationException(instruction.toString())
}
})
}
private fun collectReachableInstructions() {
val reachableFromThisPseudocode = hashSetOf<Instruction>()
traverseFollowingInstructions(
enterInstruction, reachableFromThisPseudocode, FORWARD
) { instruction ->
if (instruction is MagicInstruction && instruction.kind === MagicKind.EXHAUSTIVE_WHEN_ELSE) {
return@traverseFollowingInstructions TraverseInstructionResult.SKIP
}
TraverseInstructionResult.CONTINUE
}
// 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)
}
reachableFromThisPseudocode.forEach { (it.owner as PseudocodeImpl).reachableInstructions.add(it) }
}
private fun markDeadInstructions() {
val instructionSet = instructions.toHashSet()
for (instruction in mutableInstructionList) {
if (!instructionSet.contains(instruction)) {
(instruction as? InstructionImpl)?.markedAsDead = true
for (nextInstruction in instruction.nextInstructions) {
(nextInstruction as? InstructionImpl)?.previousInstructions?.remove(instruction)
}
}
}
}
private fun getJumpTarget(targetLabel: Label): Instruction = targetLabel.resolveToInstruction()
private fun getNextPosition(currentPosition: Int): Instruction {
val targetPosition = currentPosition + 1
assert(targetPosition < mutableInstructionList.size) { currentPosition }
return mutableInstructionList[targetPosition]
}
override fun copy(): PseudocodeImpl {
val result = PseudocodeImpl(correspondingElement, isInlined)
result.repeatWhole(this)
return result
}
override fun instructionForElement(element: KtElement): KtElementInstruction? = representativeInstructions[element]
private fun repeatWhole(originalPseudocode: PseudocodeImpl) {
repeatInternal(originalPseudocode, null, null, 0)
parent = originalPseudocode.parent
}
fun repeatPart(startLabel: Label, finishLabel: Label, labelCount: Int): Int =
repeatInternal(startLabel.pseudocode as PseudocodeImpl, startLabel, finishLabel, labelCount)
private fun repeatInternal(
originalPseudocode: PseudocodeImpl,
startLabel: Label?, finishLabel: Label?,
labelCountArg: Int
): Int {
var labelCount = labelCountArg
val startIndex = startLabel?.targetInstructionIndex ?: 0
val finishIndex = finishLabel?.targetInstructionIndex ?: originalPseudocode.mutableInstructionList.size
val originalToCopy = linkedMapOf<Label, PseudocodeLabel>()
val originalLabelsForInstruction = HashMultimap.create<Instruction, Label>()
for (label in originalPseudocode.labels) {
val index = label.targetInstructionIndex
//label is not bounded yet
if (index < 0) continue
if (label === startLabel || label === finishLabel) continue
if (index in startIndex..finishIndex) {
originalToCopy.put(label, label.copy(this, labelCount++))
originalLabelsForInstruction.put(getJumpTarget(label), label)
}
}
for (label in originalToCopy.values) {
labels.add(label)
}
for (index in startIndex until finishIndex) {
val originalInstruction = originalPseudocode.mutableInstructionList[index]
repeatLabelsBindingForInstruction(originalInstruction, originalToCopy, originalLabelsForInstruction)
val copy = copyInstruction(originalInstruction, originalToCopy)
addInstruction(copy)
if (originalInstruction === originalPseudocode.internalErrorInstruction && copy is SubroutineExitInstruction) {
internalErrorInstruction = copy
}
if (originalInstruction === originalPseudocode.internalExitInstruction && copy is SubroutineExitInstruction) {
internalExitInstruction = copy
}
if (originalInstruction === originalPseudocode.internalSinkInstruction && copy is SubroutineSinkInstruction) {
internalSinkInstruction = copy
}
}
if (finishIndex < originalPseudocode.mutableInstructionList.size) {
repeatLabelsBindingForInstruction(
originalPseudocode.mutableInstructionList[finishIndex],
originalToCopy,
originalLabelsForInstruction
)
}
return labelCount
}
private fun repeatLabelsBindingForInstruction(
originalInstruction: Instruction,
originalToCopy: Map<Label, PseudocodeLabel>,
originalLabelsForInstruction: Multimap<Instruction, Label>
) {
for (originalLabel in originalLabelsForInstruction.get(originalInstruction)) {
bindLabel(originalToCopy[originalLabel]!!)
}
}
private fun copyInstruction(instruction: Instruction, originalToCopy: Map<Label, PseudocodeLabel>): Instruction {
if (instruction is AbstractJumpInstruction) {
val originalTarget = instruction.targetLabel
if (originalToCopy.containsKey(originalTarget)) {
return instruction.copy(originalToCopy[originalTarget]!!)
}
}
if (instruction is NondeterministicJumpInstruction) {
val originalTargets = instruction.targetLabels
val copyTargets = copyLabels(originalTargets, originalToCopy)
return instruction.copy(copyTargets)
}
return (instruction as InstructionImpl).copy()
}
private fun copyLabels(labels: Collection<Label>, originalToCopy: Map<Label, PseudocodeLabel>): MutableList<Label> {
val newLabels = arrayListOf<Label>()
for (label in labels) {
val newLabel = originalToCopy[label]
newLabels.add(newLabel ?: label)
}
return newLabels
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2016 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
import org.jetbrains.kotlin.cfg.Label
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.psi.KtElement
class PseudocodeLabel internal constructor(
override val pseudocode: PseudocodeImpl, override val name: String, private val comment: String?
) : Label {
private val instructionList: List<Instruction> get() = pseudocode.mutableInstructionList
private val correspondingElement: KtElement get() = pseudocode.correspondingElement
override var targetInstructionIndex = -1
override fun toString(): String = if (comment == null) name else "$name [$comment]"
override fun resolveToInstruction(): Instruction {
val index = targetInstructionIndex
when {
index < 0 ->
error(
"resolveToInstruction: unbound label $name " +
"in subroutine ${correspondingElement.text} with instructions $instructionList"
)
index >= instructionList.size ->
error(
"resolveToInstruction: incorrect index $index for label $name " +
"in subroutine ${correspondingElement.text} with instructions $instructionList"
)
else ->
return instructionList[index]
}
}
fun copy(newPseudocode: PseudocodeImpl, newLabelIndex: Int): PseudocodeLabel =
PseudocodeLabel(newPseudocode, "L" + newLabelIndex, "copy of $name, $comment")
}
@@ -0,0 +1,151 @@
/*
* Copyright 2010-2015 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;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cfg.ControlFlowProcessor;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.AccessTarget;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.AccessValueInstruction;
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction;
import org.jetbrains.kotlin.descriptors.VariableDescriptor;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.kotlin.psi.KtElement;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.BindingTrace;
import org.jetbrains.kotlin.resolve.PropertyImportedFromObject;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.ResolvedCallUtilKt;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.util.slicedMap.ReadOnlySlice;
import org.jetbrains.kotlin.util.slicedMap.WritableSlice;
import java.util.Collection;
import static org.jetbrains.kotlin.resolve.BindingContextUtils.variableDescriptorForDeclaration;
public class PseudocodeUtil {
@NotNull
public static Pseudocode generatePseudocode(@NotNull KtDeclaration declaration, @NotNull BindingContext bindingContext) {
BindingTrace mockTrace = new BindingTrace() {
@NotNull
@Override
public BindingContext getBindingContext() {
return bindingContext;
}
@Override
public <K, V> void record(WritableSlice<K, V> slice, K key, V value) {
}
@Override
public <K> void record(WritableSlice<K, Boolean> slice, K key) {
}
@Override
public <K, V> V get(ReadOnlySlice<K, V> slice, K key) {
return bindingContext.get(slice, key);
}
@NotNull
@Override
public <K, V> Collection<K> getKeys(WritableSlice<K, V> slice) {
return bindingContext.getKeys(slice);
}
@Nullable
@Override
public KotlinType getType(@NotNull KtExpression expression) {
return bindingContext.getType(expression);
}
@Override
public void recordType(@NotNull KtExpression expression, @Nullable KotlinType type) {
}
@Override
public void report(@NotNull Diagnostic diagnostic) {
}
@Override
public boolean wantsDiagnostics() {
return false;
}
};
return new ControlFlowProcessor(mockTrace, null).generatePseudocode(declaration);
}
@Nullable
public static VariableDescriptor extractVariableDescriptorFromReference(
@NotNull Instruction instruction,
@NotNull BindingContext bindingContext
) {
if (instruction instanceof AccessValueInstruction) {
KtElement element = ((AccessValueInstruction) instruction).getElement();
if (element instanceof KtDeclaration) return null;
VariableDescriptor descriptor = extractVariableDescriptorIfAny(instruction, bindingContext);
if (descriptor instanceof PropertyImportedFromObject) {
return ((PropertyImportedFromObject) descriptor).getCallableFromObject();
}
return descriptor;
}
return null;
}
@Nullable
public static VariableDescriptor extractVariableDescriptorIfAny(
@NotNull Instruction instruction,
@NotNull BindingContext bindingContext
) {
if (instruction instanceof VariableDeclarationInstruction) {
KtDeclaration declaration = ((VariableDeclarationInstruction) instruction).getVariableDeclarationElement();
return variableDescriptorForDeclaration(bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration));
}
else if (instruction instanceof AccessValueInstruction) {
AccessTarget target = ((AccessValueInstruction) instruction).getTarget();
if (target instanceof AccessTarget.Declaration) {
return ((AccessTarget.Declaration) target).getDescriptor();
}
else if (target instanceof AccessTarget.Call) {
return variableDescriptorForDeclaration(((AccessTarget.Call) target).getResolvedCall().getResultingDescriptor());
}
}
return null;
}
// When deal with constructed object (not this) treat it like it's fully initialized
// Otherwise (this or access with empty receiver) access instruction should be handled as usual
public static boolean isThisOrNoDispatchReceiver(
@NotNull AccessValueInstruction instruction,
@NotNull BindingContext bindingContext
) {
if (instruction.getReceiverValues().isEmpty()) {
return true;
}
AccessTarget accessTarget = instruction.getTarget();
if (accessTarget instanceof AccessTarget.BlackBox) return false;
assert accessTarget instanceof AccessTarget.Call :
"AccessTarget.Declaration has no receivers and it's not BlackBox, so it should be Call";
ResolvedCall<?> accessResolvedCall = ((AccessTarget.Call) accessTarget).getResolvedCall();
return ResolvedCallUtilKt.hasThisOrNoDispatchReceiver(accessResolvedCall, bindingContext);
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2015 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
import com.intellij.util.SmartFMap
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
interface TypePredicate : (KotlinType) -> Boolean {
override fun invoke(typeToCheck: KotlinType): Boolean
}
data class SingleType(val targetType: KotlinType) : TypePredicate {
override fun invoke(typeToCheck: KotlinType): Boolean = KotlinTypeChecker.DEFAULT.equalTypes(typeToCheck, targetType)
override fun toString(): String = targetType.render()
}
data class AllSubtypes(val upperBound: KotlinType) : TypePredicate {
override fun invoke(typeToCheck: KotlinType): Boolean = KotlinTypeChecker.DEFAULT.isSubtypeOf(typeToCheck, upperBound)
override fun toString(): String = "{<: ${upperBound.render()}}"
}
data class ForAllTypes(val typeSets: List<TypePredicate>) : TypePredicate {
override fun invoke(typeToCheck: KotlinType): Boolean = typeSets.all { it(typeToCheck) }
override fun toString(): String = "AND{${typeSets.joinToString(", ")}}"
}
data class ForSomeType(val typeSets: List<TypePredicate>) : TypePredicate {
override fun invoke(typeToCheck: KotlinType): Boolean = typeSets.any { it(typeToCheck) }
override fun toString(): String = "OR{${typeSets.joinToString(", ")}}"
}
object AllTypes : TypePredicate {
override fun invoke(typeToCheck: KotlinType): Boolean = true
override fun toString(): String = "*"
}
// todo: simplify computed type predicate when possible
fun and(predicates: Collection<TypePredicate>): TypePredicate =
when (predicates.size) {
0 -> AllTypes
1 -> predicates.first()
else -> ForAllTypes(predicates.toList())
}
fun or(predicates: Collection<TypePredicate>): TypePredicate? =
when (predicates.size) {
0 -> null
1 -> predicates.first()
else -> ForSomeType(predicates.toList())
}
fun KotlinType.getSubtypesPredicate(): TypePredicate = when {
KotlinBuiltIns.isAnyOrNullableAny(this) && isMarkedNullable -> AllTypes
TypeUtils.canHaveSubtypes(KotlinTypeChecker.DEFAULT, this) -> AllSubtypes(this)
else -> SingleType(this)
}
private fun KotlinType.render(): String = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(this)
fun <T : Any> TypePredicate.expectedTypeFor(keys: Iterable<T>): Map<T, TypePredicate> =
keys.fold(SmartFMap.emptyMap<T, TypePredicate>()) { map, key -> map.plus(key, this) }
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtElement
class BlockScope(private val parentScope: BlockScope?, val block: KtElement) {
val depth: Int = (parentScope?.depth ?: 0) + 1
val blockScopeForContainingDeclaration: BlockScope? by lazy {
var scope: BlockScope? = this
while (scope != null) {
if (scope.block is KtDeclaration) {
break
}
scope = scope.parentScope
}
scope
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
interface Instruction {
var owner: Pseudocode
val previousInstructions: Collection<Instruction>
val nextInstructions: Collection<Instruction>
val dead: Boolean
val blockScope: BlockScope
val inputValues: List<PseudoValue>
val copies: Collection<Instruction>
fun accept(visitor: InstructionVisitor)
fun <R> accept(visitor: InstructionVisitorWithResult<R>): R
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2015 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
import java.util.Collections
import java.util.LinkedHashSet
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
abstract class InstructionImpl(override val blockScope: BlockScope) : Instruction {
private var _owner: Pseudocode? = null
override var owner: Pseudocode
get() = _owner!!
set(value) {
assert(_owner == null || _owner == value)
_owner = value
}
private var allCopies: MutableSet<InstructionImpl>? = null
override val copies: Collection<Instruction>
get() = allCopies?.filter { it != this } ?: Collections.emptyList()
fun copy(): Instruction = updateCopyInfo(createCopy())
protected abstract fun createCopy(): InstructionImpl
protected fun updateCopyInfo(instruction: InstructionImpl): Instruction {
if (allCopies == null) {
allCopies = hashSetOf(this)
}
instruction.allCopies = allCopies
allCopies!!.add(instruction)
return instruction
}
var markedAsDead: Boolean = false
override val dead: Boolean get() = allCopies?.all { it.markedAsDead } ?: markedAsDead
override val previousInstructions: MutableCollection<Instruction> = LinkedHashSet()
protected fun outgoingEdgeTo(target: Instruction?): Instruction? {
(target as InstructionImpl?)?.previousInstructions?.add(this)
return target
}
override val inputValues: List<PseudoValue> = Collections.emptyList()
}
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
open class InstructionVisitor {
open fun visitAccessInstruction(instruction: AccessValueInstruction) {
visitInstructionWithNext(instruction)
}
open fun visitReadValue(instruction: ReadValueInstruction) {
visitAccessInstruction(instruction)
}
open fun visitLocalFunctionDeclarationInstruction(instruction: LocalFunctionDeclarationInstruction) {
visitInstructionWithNext(instruction)
}
open fun visitInlinedLocalFunctionDeclarationInstruction(instruction: InlinedLocalFunctionDeclarationInstruction) {
visitLocalFunctionDeclarationInstruction(instruction)
}
open fun visitVariableDeclarationInstruction(instruction: VariableDeclarationInstruction) {
visitInstructionWithNext(instruction)
}
open fun visitUnconditionalJump(instruction: UnconditionalJumpInstruction) {
visitJump(instruction)
}
open fun visitConditionalJump(instruction: ConditionalJumpInstruction) {
visitJump(instruction)
}
open fun visitReturnValue(instruction: ReturnValueInstruction) {
visitJump(instruction)
}
open fun visitReturnNoValue(instruction: ReturnNoValueInstruction) {
visitJump(instruction)
}
open fun visitThrowExceptionInstruction(instruction: ThrowExceptionInstruction) {
visitJump(instruction)
}
open fun visitNondeterministicJump(instruction: NondeterministicJumpInstruction) {
visitInstruction(instruction)
}
open fun visitSubroutineExit(instruction: SubroutineExitInstruction) {
visitInstruction(instruction)
}
open fun visitSubroutineSink(instruction: SubroutineSinkInstruction) {
visitInstruction(instruction)
}
open fun visitJump(instruction: AbstractJumpInstruction) {
visitInstruction(instruction)
}
open fun visitInstructionWithNext(instruction: InstructionWithNext) {
visitInstruction(instruction)
}
open fun visitInstruction(instruction: Instruction) {
}
open fun visitSubroutineEnter(instruction: SubroutineEnterInstruction) {
visitInstructionWithNext(instruction)
}
open fun visitWriteValue(instruction: WriteValueInstruction) {
visitAccessInstruction(instruction)
}
open fun visitLoadUnitValue(instruction: LoadUnitValueInstruction) {
visitInstructionWithNext(instruction)
}
open fun visitOperation(instruction: OperationInstruction) {
visitInstructionWithNext(instruction)
}
open fun visitCallInstruction(instruction: CallInstruction) {
visitOperation(instruction)
}
open fun visitMerge(instruction: MergeInstruction) {
visitOperation(instruction)
}
open fun visitMarkInstruction(instruction: MarkInstruction) {
visitInstructionWithNext(instruction)
}
open fun visitMagic(instruction: MagicInstruction) {
visitOperation(instruction)
}
}
@@ -0,0 +1,73 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.*
abstract class InstructionVisitorWithResult<out R> {
abstract fun visitInstruction(instruction: Instruction): R
open fun visitAccessInstruction(instruction: AccessValueInstruction): R = visitInstructionWithNext(instruction)
open fun visitReadValue(instruction: ReadValueInstruction): R = visitAccessInstruction(instruction)
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)
open fun visitConditionalJump(instruction: ConditionalJumpInstruction): R = visitJump(instruction)
open fun visitReturnValue(instruction: ReturnValueInstruction): R = visitJump(instruction)
open fun visitReturnNoValue(instruction: ReturnNoValueInstruction): R = visitJump(instruction)
open fun visitThrowExceptionInstruction(instruction: ThrowExceptionInstruction): R = visitJump(instruction)
open fun visitNondeterministicJump(instruction: NondeterministicJumpInstruction): R = visitInstruction(instruction)
open fun visitSubroutineExit(instruction: SubroutineExitInstruction): R = visitInstruction(instruction)
open fun visitSubroutineSink(instruction: SubroutineSinkInstruction): R = visitInstruction(instruction)
open fun visitJump(instruction: AbstractJumpInstruction): R = visitInstruction(instruction)
open fun visitInstructionWithNext(instruction: InstructionWithNext): R = visitInstruction(instruction)
open fun visitSubroutineEnter(instruction: SubroutineEnterInstruction): R = visitInstructionWithNext(instruction)
open fun visitWriteValue(instruction: WriteValueInstruction): R = visitAccessInstruction(instruction)
open fun visitLoadUnitValue(instruction: LoadUnitValueInstruction): R = visitInstructionWithNext(instruction)
open fun visitOperation(instruction: OperationInstruction): R = visitInstructionWithNext(instruction)
open fun visitCallInstruction(instruction: CallInstruction): R = visitOperation(instruction)
open fun visitMerge(instruction: MergeInstruction): R = visitOperation(instruction)
open fun visitMarkInstruction(instruction: MarkInstruction): R = visitInstructionWithNext(instruction)
open fun visitMagic(instruction: MagicInstruction): R = visitOperation(instruction)
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.psi.KtElement
abstract class InstructionWithNext(
element: KtElement,
blockScope: BlockScope
) : KtElementInstructionImpl(element, blockScope) {
var next: Instruction? = null
set(value) {
field = outgoingEdgeTo(value)
}
override val nextInstructions: Collection<Instruction>
get() = listOfNotNull(next)
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2015 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
import org.jetbrains.kotlin.psi.KtElement
interface KtElementInstruction : Instruction {
val element: KtElement
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2015 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
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.psi.KtElement
abstract class KtElementInstructionImpl(
override val element: KtElement,
blockScope: BlockScope
) : InstructionImpl(blockScope), KtElementInstruction {
protected fun render(element: PsiElement): String =
element.text?.replace("\\s+".toRegex(), " ") ?: ""
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2015 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.eval
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
interface InstructionWithReceivers : Instruction {
val receiverValues: Map<PseudoValue, ReceiverValue>
}
@@ -0,0 +1,24 @@
/*
* Copyright 2010-2015 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.eval
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstruction
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
interface InstructionWithValue : KtElementInstruction {
val outputValue: PseudoValue?
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2015 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.eval
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionWithNext
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
class LoadUnitValueInstruction(
expression: KtExpression,
blockScope: BlockScope
) : InstructionWithNext(expression, blockScope) {
override fun accept(visitor: InstructionVisitor) {
visitor.visitLoadUnitValue(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R {
return visitor.visitLoadUnitValue(this)
}
override fun toString(): String =
"read (Unit)"
override fun createCopy(): InstructionImpl =
LoadUnitValueInstruction(element as KtExpression, blockScope)
}
@@ -0,0 +1,129 @@
/*
* Copyright 2010-2015 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.eval
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValueFactory
import org.jetbrains.kotlin.cfg.pseudocode.instructions.*
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
sealed class AccessTarget {
class Declaration(val descriptor: VariableDescriptor) : AccessTarget() {
override fun equals(other: Any?) = other is Declaration && descriptor == other.descriptor
override fun hashCode() = descriptor.hashCode()
}
class Call(val resolvedCall: ResolvedCall<*>) : AccessTarget() {
override fun equals(other: Any?) = other is Call && resolvedCall == other.resolvedCall
override fun hashCode() = resolvedCall.hashCode()
}
object BlackBox : AccessTarget()
}
val AccessTarget.accessedDescriptor: CallableDescriptor?
get() = when (this) {
is AccessTarget.Declaration -> descriptor
is AccessTarget.Call -> resolvedCall.resultingDescriptor
is AccessTarget.BlackBox -> null
}
abstract class AccessValueInstruction protected constructor(
element: KtElement,
blockScope: BlockScope,
val target: AccessTarget,
override val receiverValues: Map<PseudoValue, ReceiverValue>
) : InstructionWithNext(element, blockScope), InstructionWithReceivers
class ReadValueInstruction private constructor(
element: KtElement,
blockScope: BlockScope,
target: AccessTarget,
receiverValues: Map<PseudoValue, ReceiverValue>,
private var _outputValue: PseudoValue?
) : AccessValueInstruction(element, blockScope, target, receiverValues), InstructionWithValue {
constructor(
element: KtElement,
blockScope: BlockScope,
target: AccessTarget,
receiverValues: Map<PseudoValue, ReceiverValue>,
factory: PseudoValueFactory
) : this(element, blockScope, target, receiverValues, null) {
_outputValue = factory.newValue(element, this)
}
override val inputValues: List<PseudoValue>
get() = receiverValues.keys.toList()
override val outputValue: PseudoValue
get() = _outputValue!!
override fun accept(visitor: InstructionVisitor) {
visitor.visitReadValue(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitReadValue(this)
override fun toString(): String {
val inVal = if (receiverValues.isEmpty()) "" else "|${receiverValues.keys.joinToString()}"
val targetName = when (target) {
is AccessTarget.Declaration -> target.descriptor
is AccessTarget.Call -> target.resolvedCall.resultingDescriptor
else -> null
}?.name?.asString()
val elementText = render(element)
val description = if (targetName != null && targetName != elementText) "$elementText, $targetName" else elementText
return "r($description$inVal) -> $outputValue"
}
override fun createCopy(): InstructionImpl =
ReadValueInstruction(element, blockScope, target, receiverValues, outputValue)
}
class WriteValueInstruction(
assignment: KtElement,
blockScope: BlockScope,
target: AccessTarget,
receiverValues: Map<PseudoValue, ReceiverValue>,
val lValue: KtElement,
private val rValue: PseudoValue
) : AccessValueInstruction(assignment, blockScope, target, receiverValues) {
override val inputValues: List<PseudoValue>
get() = (receiverValues.keys as Collection<PseudoValue>) + rValue
override fun accept(visitor: InstructionVisitor) {
visitor.visitWriteValue(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitWriteValue(this)
override fun toString(): String {
val lhs = (lValue as? KtNamedDeclaration)?.name ?: render(lValue)
return "w($lhs|${inputValues.joinToString(", ")})"
}
override fun createCopy(): InstructionImpl =
WriteValueInstruction(element, blockScope, target, receiverValues, lValue, rValue)
}
@@ -0,0 +1,171 @@
/*
* Copyright 2010-2015 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.eval
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValueFactory
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionWithNext
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
abstract class OperationInstruction protected constructor(
element: KtElement,
blockScope: BlockScope,
override val inputValues: List<PseudoValue>
) : InstructionWithNext(element, blockScope), InstructionWithValue {
protected var resultValue: PseudoValue? = null
override val outputValue: PseudoValue?
get() = resultValue
protected fun renderInstruction(name: String, desc: String): String =
"$name($desc" +
(if (inputValues.isNotEmpty()) "|${inputValues.joinToString(", ")})" else ")") +
(if (resultValue != null) " -> $resultValue" else "")
protected fun setResult(value: PseudoValue?): OperationInstruction {
this.resultValue = value
return this
}
protected fun setResult(factory: PseudoValueFactory?, valueElement: KtElement? = element): OperationInstruction =
setResult(factory?.newValue(valueElement, this))
}
class CallInstruction private constructor(
element: KtElement,
blockScope: BlockScope,
val resolvedCall: ResolvedCall<*>,
override val receiverValues: Map<PseudoValue, ReceiverValue>,
val arguments: Map<PseudoValue, ValueParameterDescriptor>
) : OperationInstruction(element, blockScope, (receiverValues.keys as Collection<PseudoValue>) + arguments.keys), InstructionWithReceivers {
constructor (
element: KtElement,
blockScope: BlockScope,
resolvedCall: ResolvedCall<*>,
receiverValues: Map<PseudoValue, ReceiverValue>,
arguments: Map<PseudoValue, ValueParameterDescriptor>,
factory: PseudoValueFactory?
) : this(element, blockScope, resolvedCall, receiverValues, arguments) {
setResult(factory)
}
override fun accept(visitor: InstructionVisitor) {
visitor.visitCallInstruction(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitCallInstruction(this)
override fun createCopy() =
CallInstruction(element, blockScope, resolvedCall, receiverValues, arguments).setResult(resultValue)
override fun toString() =
renderInstruction("call", "${render(element)}, ${resolvedCall.resultingDescriptor!!.name}")
}
// Introduces black-box operation
// Used to:
// consume input values (so that they aren't considered unused)
// denote value transformation which can't be expressed by other instructions (such as call or read)
// pass more than one value to instruction which formally requires only one (e.g. jump)
class MagicInstruction(
element: KtElement,
blockScope: BlockScope,
inputValues: List<PseudoValue>,
val kind: MagicKind
) : OperationInstruction(element, blockScope, inputValues) {
constructor (
element: KtElement,
valueElement: KtElement?,
blockScope: BlockScope,
inputValues: List<PseudoValue>,
kind: MagicKind,
factory: PseudoValueFactory
) : this(element, blockScope, inputValues, kind) {
setResult(factory, valueElement)
}
val synthetic: Boolean get() = outputValue.element == null
override val outputValue: PseudoValue
get() = resultValue!!
override fun accept(visitor: InstructionVisitor) = visitor.visitMagic(this)
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitMagic(this)
override fun createCopy() =
MagicInstruction(element, blockScope, inputValues, kind).setResult(resultValue)
override fun toString() = renderInstruction("magic[$kind]", render(element))
}
enum class MagicKind(val sideEffectFree: Boolean = false) {
// builtin operations
STRING_TEMPLATE(true),
AND(true),
OR(true),
NOT_NULL_ASSERTION(),
EQUALS_IN_WHEN_CONDITION(),
IS(),
CAST(),
UNBOUND_CALLABLE_REFERENCE(true),
BOUND_CALLABLE_REFERENCE(true),
// implicit operations
LOOP_RANGE_ITERATION(),
IMPLICIT_RECEIVER(),
VALUE_CONSUMER(),
// unrecognized operations
UNRESOLVED_CALL(),
UNSUPPORTED_ELEMENT(),
UNRECOGNIZED_WRITE_RHS(),
FAKE_INITIALIZER(),
EXHAUSTIVE_WHEN_ELSE()
}
// Merges values produced by alternative control-flow paths (such as 'if' branches)
class MergeInstruction private constructor(
element: KtElement,
blockScope: BlockScope,
inputValues: List<PseudoValue>
) : OperationInstruction(element, blockScope, inputValues) {
constructor (
element: KtElement,
blockScope: BlockScope,
inputValues: List<PseudoValue>,
factory: PseudoValueFactory
) : this(element, blockScope, inputValues) {
setResult(factory)
}
override val outputValue: PseudoValue
get() = resultValue!!
override fun accept(visitor: InstructionVisitor) = visitor.visitMerge(this)
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitMerge(this)
override fun createCopy() = MergeInstruction(element, blockScope, inputValues).setResult(resultValue)
override fun toString() = renderInstruction("merge", render(element))
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.cfg.Label
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstructionImpl
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
abstract class AbstractJumpInstruction(
element: KtElement,
val targetLabel: Label,
blockScope: BlockScope
) : KtElementInstructionImpl(element, blockScope), JumpInstruction {
var resolvedTarget: Instruction? = null
set(value) {
field = outgoingEdgeTo(value)
}
protected abstract fun createCopy(newLabel: Label, blockScope: BlockScope): AbstractJumpInstruction
fun copy(newLabel: Label): Instruction = updateCopyInfo(createCopy(newLabel, blockScope))
override fun createCopy(): InstructionImpl = createCopy(targetLabel, blockScope)
override val nextInstructions: Collection<Instruction>
get() = listOfNotNull(resolvedTarget)
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.cfg.Label
import java.util.Arrays
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
class ConditionalJumpInstruction(
element: KtElement,
val onTrue: Boolean,
blockScope: BlockScope,
targetLabel: Label,
private val conditionValue: PseudoValue?
) : AbstractJumpInstruction(element, targetLabel, blockScope) {
private var _nextOnTrue: Instruction? = null
private var _nextOnFalse: Instruction? = null
var nextOnTrue: Instruction
get() = _nextOnTrue!!
set(value) {
_nextOnTrue = outgoingEdgeTo(value)
}
var nextOnFalse: Instruction
get() = _nextOnFalse!!
set(value) {
_nextOnFalse = outgoingEdgeTo(value)
}
override val nextInstructions: Collection<Instruction>
get() = listOf(nextOnFalse, nextOnTrue)
override val inputValues: List<PseudoValue>
get() = listOfNotNull(conditionValue)
override fun accept(visitor: InstructionVisitor) {
visitor.visitConditionalJump(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R {
return visitor.visitConditionalJump(this)
}
override fun toString(): String {
val instr = if (onTrue) "jt" else "jf"
val inValue = conditionValue?.let { "|" + it } ?: ""
return "$instr(${targetLabel.name}$inValue)"
}
override fun createCopy(newLabel: Label, blockScope: BlockScope): AbstractJumpInstruction =
ConditionalJumpInstruction(element, onTrue, blockScope, newLabel, conditionValue)
}
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
interface JumpInstruction : Instruction
@@ -0,0 +1,80 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.cfg.Label
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.KtElementInstructionImpl
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
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 NondeterministicJumpInstruction(
element: KtElement,
targetLabels: List<Label>,
blockScope: BlockScope,
private val inputValue: PseudoValue?
) : KtElementInstructionImpl(element, blockScope), JumpInstruction {
private var _next: Instruction? = null
private val _resolvedTargets: MutableMap<Label, Instruction> = linkedMapOf()
val targetLabels: List<Label> = ArrayList(targetLabels)
private val resolvedTargets: Map<Label, Instruction>
get() = _resolvedTargets
fun setResolvedTarget(label: Label, resolvedTarget: Instruction) {
_resolvedTargets[label] = outgoingEdgeTo(resolvedTarget)!!
}
var next: Instruction
get() = _next!!
set(value) {
_next = outgoingEdgeTo(value)
}
override val nextInstructions: Collection<Instruction>
get() {
val targetInstructions = ArrayList(resolvedTargets.values)
targetInstructions.add(next)
return targetInstructions
}
override val inputValues: List<PseudoValue>
get() = listOfNotNull(inputValue)
override fun accept(visitor: InstructionVisitor) {
visitor.visitNondeterministicJump(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitNondeterministicJump(this)
override fun toString(): String {
val inVal = if (inputValue != null) "|$inputValue" else ""
val labels = targetLabels.joinToString(", ") { it.name }
return "jmp?($labels$inVal)"
}
override fun createCopy(): InstructionImpl = createCopy(targetLabels)
fun copy(newTargetLabels: MutableList<Label>): Instruction = updateCopyInfo(createCopy(newTargetLabels))
private fun createCopy(newTargetLabels: List<Label>): InstructionImpl =
NondeterministicJumpInstruction(element, newTargetLabels, blockScope, inputValue)
}
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.cfg.Label
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
class ReturnNoValueInstruction(
element: KtElement,
blockScope: BlockScope,
targetLabel: Label,
val subroutine: KtElement
) : AbstractJumpInstruction(element, targetLabel, blockScope) {
override fun accept(visitor: InstructionVisitor) {
visitor.visitReturnNoValue(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitReturnNoValue(this)
override fun toString(): String = "ret $targetLabel"
override fun createCopy(newLabel: Label, blockScope: BlockScope): AbstractJumpInstruction =
ReturnNoValueInstruction(element, blockScope, newLabel, subroutine)
}
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.cfg.Label
import java.util.Collections
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtReturnExpression
class ReturnValueInstruction(
returnExpression: KtExpression,
blockScope: BlockScope,
targetLabel: Label,
val returnedValue: PseudoValue,
val subroutine: KtElement
) : AbstractJumpInstruction(returnExpression, targetLabel, blockScope) {
override val inputValues: List<PseudoValue> get() = Collections.singletonList(returnedValue)
override fun accept(visitor: InstructionVisitor) {
visitor.visitReturnValue(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitReturnValue(this)
override fun toString(): String = "ret(*|$returnedValue) $targetLabel"
override fun createCopy(newLabel: Label, blockScope: BlockScope): AbstractJumpInstruction =
ReturnValueInstruction((element as KtExpression), blockScope, newLabel, returnedValue, subroutine)
val returnExpressionIfAny: KtReturnExpression? = element as? KtReturnExpression
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.cfg.pseudocode.PseudoValue
import org.jetbrains.kotlin.psi.KtThrowExpression
import org.jetbrains.kotlin.cfg.Label
import java.util.Collections
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
class ThrowExceptionInstruction(
expression: KtThrowExpression,
blockScope: BlockScope,
errorLabel: Label,
private val thrownValue: PseudoValue
) : AbstractJumpInstruction(expression, errorLabel, blockScope) {
override val inputValues: List<PseudoValue> get() = Collections.singletonList(thrownValue)
override fun accept(visitor: InstructionVisitor) {
visitor.visitThrowExceptionInstruction(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitThrowExceptionInstruction(this)
override fun toString(): String = "throw (${element.text}|$thrownValue)"
override fun createCopy(newLabel: Label, blockScope: BlockScope): AbstractJumpInstruction =
ThrowExceptionInstruction((element as KtThrowExpression), blockScope, newLabel, thrownValue)
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2015 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.jumps
import org.jetbrains.kotlin.cfg.pseudocode.instructions.*
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.cfg.Label
class UnconditionalJumpInstruction(
element: KtElement,
targetLabel: Label,
blockScope: BlockScope
) : AbstractJumpInstruction(element, targetLabel, blockScope) {
override fun accept(visitor: InstructionVisitor) {
visitor.visitUnconditionalJump(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitUnconditionalJump(this)
override fun toString(): String = "jmp(${targetLabel.name})"
override fun createCopy(newLabel: Label, blockScope: BlockScope): AbstractJumpInstruction =
UnconditionalJumpInstruction(element, newLabel, blockScope)
}
@@ -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.EventOccurrencesRange
import org.jetbrains.kotlin.psi.KtElement
class InlinedLocalFunctionDeclarationInstruction(
element: KtElement,
body: Pseudocode,
blockScope: BlockScope,
val kind: EventOccurrencesRange
) : 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)})"
}
@@ -0,0 +1,58 @@
/*
* Copyright 2010-2015 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.psi.KtElement
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionWithNext
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl
open class LocalFunctionDeclarationInstruction(
element: KtElement,
val body: Pseudocode,
blockScope: BlockScope
) : InstructionWithNext(element, blockScope) {
var sink: SubroutineSinkInstruction? = null
set(value) {
field = outgoingEdgeTo(value) as SubroutineSinkInstruction?
}
override val nextInstructions: Collection<Instruction>
get() {
sink?.let {
val instructions = arrayListOf<Instruction>(it)
instructions.addAll(super.nextInstructions)
return instructions
}
return super.nextInstructions
}
override fun accept(visitor: InstructionVisitor) {
visitor.visitLocalFunctionDeclarationInstruction(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitLocalFunctionDeclarationInstruction(this)
override fun toString(): String = "d(${render(element)})"
override fun createCopy(): InstructionImpl =
LocalFunctionDeclarationInstruction(element, body.copy(), blockScope)
}
@@ -0,0 +1,39 @@
/*
* Copyright 2010-2015 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.psi.KtElement
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionWithNext
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
class MarkInstruction(
element: KtElement,
blockScope: BlockScope
) : InstructionWithNext(element, blockScope) {
override fun accept(visitor: InstructionVisitor) {
visitor.visitMarkInstruction(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitMarkInstruction(this)
override fun createCopy() = MarkInstruction(element, blockScope)
override fun toString() = "mark(${render(element)})"
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2015 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.psi.KtElement
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionWithNext
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 SubroutineEnterInstruction(
val subroutine: KtElement,
blockScope: BlockScope
) : InstructionWithNext(subroutine, blockScope) {
override fun accept(visitor: InstructionVisitor) {
visitor.visitSubroutineEnter(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitSubroutineEnter(this)
override fun toString(): String = "<START>"
override fun createCopy(): InstructionImpl =
SubroutineEnterInstruction(subroutine, blockScope)
}
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2015 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.instructions.*
import org.jetbrains.kotlin.psi.KtElement
import java.util.*
class SubroutineExitInstruction(
val subroutine: KtElement,
blockScope: BlockScope,
val isError: Boolean
) : InstructionImpl(blockScope) {
private var _sink: SubroutineSinkInstruction? = null
var sink: SubroutineSinkInstruction
get() = _sink!!
set(value: SubroutineSinkInstruction) {
_sink = outgoingEdgeTo(value) as SubroutineSinkInstruction
}
override val nextInstructions: Collection<Instruction>
get() = Collections.singleton(sink)
override fun accept(visitor: InstructionVisitor) {
visitor.visitSubroutineExit(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitSubroutineExit(this)
override fun toString(): String = if (isError) "<ERROR>" else "<END>"
override fun createCopy(): InstructionImpl =
SubroutineExitInstruction(subroutine, blockScope, isError)
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2015 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.psi.KtElement
import java.util.Collections
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
class SubroutineSinkInstruction(
val subroutine: KtElement,
blockScope: BlockScope,
private val debugLabel: String
) : InstructionImpl(blockScope) {
override val nextInstructions: Collection<Instruction>
get() = Collections.emptyList()
override fun accept(visitor: InstructionVisitor) {
visitor.visitSubroutineSink(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitSubroutineSink(this)
override fun toString(): String = debugLabel
override fun createCopy(): InstructionImpl =
SubroutineSinkInstruction(subroutine, blockScope, debugLabel)
}
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2015 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.instructions.InstructionWithNext
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitor
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionVisitorWithResult
import org.jetbrains.kotlin.cfg.pseudocode.instructions.InstructionImpl
import org.jetbrains.kotlin.psi.*
class VariableDeclarationInstruction(
element: KtDeclaration,
blockScope: BlockScope
) : InstructionWithNext(element, blockScope) {
init {
assert(element is KtVariableDeclaration || element is KtParameter || element is KtEnumEntry || element is KtObjectDeclaration) {
"Invalid element: ${render(element)}}"
}
}
val variableDeclarationElement: KtDeclaration
get() = element as KtDeclaration
override fun accept(visitor: InstructionVisitor) {
visitor.visitVariableDeclarationInstruction(this)
}
override fun <R> accept(visitor: InstructionVisitorWithResult<R>): R = visitor.visitVariableDeclarationInstruction(this)
override fun toString(): String = "v(${render(element)})"
override fun createCopy(): InstructionImpl =
VariableDeclarationInstruction(variableDeclarationElement, blockScope)
}
@@ -0,0 +1,300 @@
/*
* 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
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cfg.Label
import org.jetbrains.kotlin.cfg.containingDeclarationForPseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind.*
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ConditionalJumpInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ReturnValueInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.jumps.ThrowExceptionInstruction
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContext.*
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
import org.jetbrains.kotlin.resolve.bindingContextUtil.getReferenceTargets
import org.jetbrains.kotlin.resolve.bindingContextUtil.getTargetFunctionDescriptor
import org.jetbrains.kotlin.resolve.calls.ValueArgumentsToParametersMapper
import org.jetbrains.kotlin.resolve.calls.callUtil.getCall
import org.jetbrains.kotlin.resolve.calls.callUtil.isSafeCall
import org.jetbrains.kotlin.resolve.calls.model.*
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.getExplicitReceiverValue
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfo
import org.jetbrains.kotlin.resolve.calls.tasks.ExplicitReceiverKind
import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate
import org.jetbrains.kotlin.resolve.calls.tasks.TracingStrategy
import org.jetbrains.kotlin.resolve.findTopMostOverriddenDescriptors
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import java.util.*
fun getReceiverTypePredicate(resolvedCall: ResolvedCall<*>, receiverValue: ReceiverValue): TypePredicate? {
val callableDescriptor = resolvedCall.resultingDescriptor ?: return null
when (receiverValue) {
resolvedCall.extensionReceiver -> {
val receiverParameter = callableDescriptor.extensionReceiverParameter
if (receiverParameter != null) return receiverParameter.type.getSubtypesPredicate()
}
resolvedCall.dispatchReceiver -> {
val rootCallableDescriptors = callableDescriptor.findTopMostOverriddenDescriptors()
return or(rootCallableDescriptors.mapNotNull {
it.dispatchReceiverParameter?.type?.let { TypeUtils.makeNullableIfNeeded(it, resolvedCall.call.isSafeCall()) }
?.getSubtypesPredicate()
})
}
}
return null
}
fun getExpectedTypePredicate(
value: PseudoValue,
bindingContext: BindingContext,
builtIns: KotlinBuiltIns
): TypePredicate {
val pseudocode = value.createdAt?.owner ?: return AllTypes
val typePredicates = LinkedHashSet<TypePredicate?>()
fun addSubtypesOf(jetType: KotlinType?) = typePredicates.add(jetType?.getSubtypesPredicate())
fun addByExplicitReceiver(resolvedCall: ResolvedCall<*>?) {
val receiverValue = (resolvedCall ?: return).getExplicitReceiverValue()
if (receiverValue != null) typePredicates.add(getReceiverTypePredicate(resolvedCall, receiverValue))
}
fun getTypePredicateForUnresolvedCallArgument(to: KtElement, inputValueIndex: Int): TypePredicate? {
if (inputValueIndex < 0) return null
val call = to.getCall(bindingContext) ?: return null
val callee = call.calleeExpression ?: return null
val candidates = callee.getReferenceTargets(bindingContext)
.filterIsInstance<FunctionDescriptor>()
.sortedBy { DescriptorRenderer.FQ_NAMES_IN_TYPES.render(it) }
if (candidates.isEmpty()) return null
val explicitReceiver = call.explicitReceiver
val argValueOffset = if (explicitReceiver != null) 1 else 0
val predicates = ArrayList<TypePredicate>()
for (candidate in candidates) {
val resolutionCandidate = ResolutionCandidate.create(
call,
candidate,
null,
ExplicitReceiverKind.NO_EXPLICIT_RECEIVER,
null
)
val candidateCall = ResolvedCallImpl.create(
resolutionCandidate,
DelegatingBindingTrace(bindingContext, "Compute type predicates for unresolved call arguments"),
TracingStrategy.EMPTY,
DataFlowInfoForArgumentsImpl(DataFlowInfo.EMPTY, call)
)
val status = ValueArgumentsToParametersMapper.mapValueArgumentsToParameters(call, TracingStrategy.EMPTY, candidateCall)
if (!status.isSuccess) continue
val candidateArgumentMap = candidateCall.valueArguments
val callArguments = call.valueArguments
val i = inputValueIndex - argValueOffset
if (i < 0 || i >= callArguments.size) continue
val mapping = candidateCall.getArgumentMapping(callArguments[i]) as? ArgumentMatch ?: continue
val candidateParameter = mapping.valueParameter
val resolvedArgument = candidateArgumentMap[candidateParameter]
val expectedType = if (resolvedArgument is VarargValueArgument)
candidateParameter.varargElementType
else
candidateParameter.type
predicates.add(if (expectedType != null) AllSubtypes(expectedType) else AllTypes)
}
return or(predicates)
}
fun addTypePredicates(value: PseudoValue) {
pseudocode.getUsages(value).forEach {
when (it) {
is ReturnValueInstruction -> {
val returnElement = it.element
val functionDescriptor = when (returnElement) {
is KtReturnExpression -> returnElement.getTargetFunctionDescriptor(bindingContext)
else -> bindingContext[DECLARATION_TO_DESCRIPTOR, pseudocode.correspondingElement]
}
addSubtypesOf((functionDescriptor as? CallableDescriptor)?.returnType)
}
is ConditionalJumpInstruction ->
addSubtypesOf(builtIns.booleanType)
is ThrowExceptionInstruction ->
addSubtypesOf(builtIns.throwable.defaultType)
is MergeInstruction ->
addTypePredicates(it.outputValue)
is AccessValueInstruction -> {
val accessTarget = it.target
val receiverValue = it.receiverValues[value]
if (receiverValue != null) {
typePredicates.add(getReceiverTypePredicate((accessTarget as AccessTarget.Call).resolvedCall, receiverValue))
} else {
val expectedType = when (accessTarget) {
is AccessTarget.Call ->
(accessTarget.resolvedCall.resultingDescriptor as? VariableDescriptor)?.type
is AccessTarget.Declaration ->
accessTarget.descriptor.type
else ->
null
}
addSubtypesOf(expectedType)
}
}
is CallInstruction -> {
val receiverValue = it.receiverValues[value]
if (receiverValue != null) {
typePredicates.add(getReceiverTypePredicate(it.resolvedCall, receiverValue))
} else {
it.arguments[value]?.let { parameter ->
val expectedType = when (it.resolvedCall.valueArguments[parameter]) {
is VarargValueArgument ->
parameter.varargElementType
else ->
parameter.type
}
addSubtypesOf(expectedType)
}
}
}
is MagicInstruction -> @Suppress("NON_EXHAUSTIVE_WHEN") when (it.kind) {
AND, OR ->
addSubtypesOf(builtIns.booleanType)
LOOP_RANGE_ITERATION ->
addByExplicitReceiver(bindingContext[LOOP_RANGE_ITERATOR_RESOLVED_CALL, value.element as? KtExpression])
VALUE_CONSUMER -> {
val element = it.element
when (element) {
element.getStrictParentOfType<KtWhileExpression>()?.condition -> addSubtypesOf(builtIns.booleanType)
is KtProperty -> {
val propertyDescriptor = bindingContext[DECLARATION_TO_DESCRIPTOR, element] as? PropertyDescriptor
propertyDescriptor?.accessors?.map {
addByExplicitReceiver(bindingContext[DELEGATED_PROPERTY_RESOLVED_CALL, it])
}
}
is KtDelegatedSuperTypeEntry -> addSubtypesOf(bindingContext[TYPE, element.typeReference])
}
}
UNRESOLVED_CALL -> {
val typePredicate = getTypePredicateForUnresolvedCallArgument(it.element, it.inputValues.indexOf(value))
typePredicates.add(typePredicate)
}
}
}
}
}
addTypePredicates(value)
return and(typePredicates.filterNotNull())
}
fun Instruction.getPrimaryDeclarationDescriptorIfAny(bindingContext: BindingContext): DeclarationDescriptor? {
return when (this) {
is CallInstruction -> return resolvedCall.resultingDescriptor
else -> PseudocodeUtil.extractVariableDescriptorIfAny(this, bindingContext)
}
}
val Instruction.sideEffectFree: Boolean
get() = owner.isSideEffectFree(this)
fun Instruction.calcSideEffectFree(): Boolean {
if (this !is InstructionWithValue) return false
if (!inputValues.all { it.createdAt?.sideEffectFree == true }) return false
return when (this) {
is ReadValueInstruction -> target.let {
when (it) {
is AccessTarget.Call -> when (it.resolvedCall.resultingDescriptor) {
is LocalVariableDescriptor, is ValueParameterDescriptor, is ReceiverParameterDescriptor -> true
else -> false
}
else -> when (element) {
is KtNamedFunction -> element.name == null
is KtConstantExpression, is KtLambdaExpression, is KtStringTemplateExpression -> true
else -> false
}
}
}
is MagicInstruction -> kind.sideEffectFree
else -> false
}
}
fun Pseudocode.getElementValuesRecursively(element: KtElement): List<PseudoValue> {
val results = ArrayList<PseudoValue>()
fun Pseudocode.collectValues() {
getElementValue(element)?.let { results.add(it) }
for (localFunction in localDeclarations) {
localFunction.body.collectValues()
}
}
collectValues()
return results
}
fun KtDeclaration.getContainingPseudocode(context: BindingContext): Pseudocode? {
val enclosingPseudocodeDeclaration = (this as? KtFunctionLiteral)?.let {
it.parents.firstOrNull { it is KtDeclaration && it !is KtFunctionLiteral } as? KtDeclaration
} ?: this
val enclosingPseudocode = PseudocodeUtil.generatePseudocode(enclosingPseudocodeDeclaration, context)
return enclosingPseudocode.getPseudocodeByElement(this)
}
fun KtElement.getContainingPseudocode(context: BindingContext) = containingDeclarationForPseudocode?.getContainingPseudocode(context)
fun Pseudocode.getPseudocodeByElement(element: KtElement): Pseudocode? {
if (correspondingElement == element) return this
localDeclarations.forEach { decl -> decl.body.getPseudocodeByElement(element)?.let { return it } }
return null
}
val Label.isJumpToError: Boolean
get() = resolveToInstruction() == pseudocode.errorInstruction
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cfg.variable
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.instructions.BlockScope
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.Edges
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.collectData
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContextUtils
class PseudocodeVariableDataCollector(
private val bindingContext: BindingContext,
private val pseudocode: Pseudocode
) {
val blockScopeVariableInfo = computeBlockScopeVariableInfo(pseudocode)
fun <I : VariableUsageControlFlowInfo<*, *>> collectData(
traversalOrder: TraversalOrder,
initialInfo: I,
instructionDataMergeStrategy: (Instruction, Collection<I>) -> Edges<I>
): Map<Instruction, Edges<I>> {
return pseudocode.collectData(
traversalOrder,
instructionDataMergeStrategy,
{ from, to, info -> filterOutVariablesOutOfScope(from, to, info) },
initialInfo
)
}
private fun <I : VariableUsageControlFlowInfo<*, *>> filterOutVariablesOutOfScope(
from: Instruction,
to: Instruction,
info: I
): I {
// If an edge goes from deeper scope to a less deep one, this means that it points outside of the deeper scope.
val toDepth = to.blockScope.depth
if (toDepth >= from.blockScope.depth) return info
// Variables declared in an inner (deeper) scope can't be accessed from an outer scope.
// Thus they can be filtered out upon leaving the inner scope.
@Suppress("UNCHECKED_CAST")
return info.retainAll { variable ->
val blockScope = blockScopeVariableInfo.declaredIn[variable]
// '-1' for variables declared outside this pseudocode
val depth = blockScope?.depth ?: -1
depth <= toDepth
} as I
}
private fun computeBlockScopeVariableInfo(pseudocode: Pseudocode): BlockScopeVariableInfo {
val blockScopeVariableInfo = BlockScopeVariableInfoImpl()
pseudocode.traverse(TraversalOrder.FORWARD) { instruction ->
if (instruction is VariableDeclarationInstruction) {
val variableDeclarationElement = instruction.variableDeclarationElement
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, variableDeclarationElement) ?: return@traverse
val variableDescriptor = BindingContextUtils.variableDescriptorForDeclaration(descriptor)
?: throw AssertionError(
"Variable or class descriptor should correspond to " +
"the instruction for ${instruction.element.text}.\n" +
"Descriptor: $descriptor"
)
blockScopeVariableInfo.registerVariableDeclaredInScope(variableDescriptor, instruction.blockScope)
}
}
return blockScopeVariableInfo
}
}
interface BlockScopeVariableInfo {
val declaredIn: Map<VariableDescriptor, BlockScope>
val scopeVariables: Map<BlockScope, Collection<VariableDescriptor>>
}
class BlockScopeVariableInfoImpl : BlockScopeVariableInfo {
override val declaredIn = HashMap<VariableDescriptor, BlockScope>()
override val scopeVariables = HashMap<BlockScope, MutableCollection<VariableDescriptor>>()
fun registerVariableDeclaredInScope(variable: VariableDescriptor, blockScope: BlockScope) {
declaredIn[variable] = blockScope
val variablesInScope = scopeVariables.getOrPut(blockScope, { arrayListOf() })
variablesInScope.add(variable)
}
}
@@ -0,0 +1,470 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cfg.variable
import org.jetbrains.kotlin.cfg.pseudocode.Pseudocode
import org.jetbrains.kotlin.cfg.pseudocode.PseudocodeUtil
import org.jetbrains.kotlin.cfg.pseudocode.instructions.Instruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.MagicKind
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.ReadValueInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.eval.WriteValueInstruction
import org.jetbrains.kotlin.cfg.pseudocode.instructions.special.VariableDeclarationInstruction
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.Edges
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.TraversalOrder
import org.jetbrains.kotlin.cfg.pseudocodeTraverser.traverse
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingContextUtils.variableDescriptorForDeclaration
import org.jetbrains.kotlin.util.javaslang.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class PseudocodeVariablesData(val pseudocode: Pseudocode, private val bindingContext: BindingContext) {
private val containsDoWhile = pseudocode.rootPseudocode.containsDoWhile
private val pseudocodeVariableDataCollector =
PseudocodeVariableDataCollector(bindingContext, pseudocode)
private class VariablesForDeclaration(
val valsWithTrivialInitializer: Set<VariableDescriptor>,
val nonTrivialVariables: Set<VariableDescriptor>
) {
val allVars =
if (nonTrivialVariables.isEmpty())
valsWithTrivialInitializer
else
LinkedHashSet(valsWithTrivialInitializer).also { it.addAll(nonTrivialVariables) }
}
private val declaredVariablesForDeclaration = hashMapOf<Pseudocode, VariablesForDeclaration>()
private val rootVariables by lazy(LazyThreadSafetyMode.NONE) {
getAllDeclaredVariables(pseudocode, includeInsideLocalDeclarations = true)
}
val variableInitializers: Map<Instruction, Edges<VariableInitReadOnlyControlFlowInfo>> by lazy {
computeVariableInitializers()
}
val blockScopeVariableInfo: BlockScopeVariableInfo
get() = pseudocodeVariableDataCollector.blockScopeVariableInfo
fun getDeclaredVariables(pseudocode: Pseudocode, includeInsideLocalDeclarations: Boolean): Set<VariableDescriptor> =
getAllDeclaredVariables(pseudocode, includeInsideLocalDeclarations).allVars
fun isVariableWithTrivialInitializer(variableDescriptor: VariableDescriptor) =
variableDescriptor in rootVariables.valsWithTrivialInitializer
private fun getAllDeclaredVariables(pseudocode: Pseudocode, includeInsideLocalDeclarations: Boolean): VariablesForDeclaration {
if (!includeInsideLocalDeclarations) {
return getUpperLevelDeclaredVariables(pseudocode)
}
val nonTrivialVariables = linkedSetOf<VariableDescriptor>()
val valsWithTrivialInitializer = linkedSetOf<VariableDescriptor>()
addVariablesFromPseudocode(pseudocode, nonTrivialVariables, valsWithTrivialInitializer)
for (localFunctionDeclarationInstruction in pseudocode.localDeclarations) {
val localPseudocode = localFunctionDeclarationInstruction.body
addVariablesFromPseudocode(localPseudocode, nonTrivialVariables, valsWithTrivialInitializer)
}
return VariablesForDeclaration(
valsWithTrivialInitializer,
nonTrivialVariables
)
}
private fun addVariablesFromPseudocode(
pseudocode: Pseudocode,
nonTrivialVariables: MutableSet<VariableDescriptor>,
valsWithTrivialInitializer: MutableSet<VariableDescriptor>
) {
getUpperLevelDeclaredVariables(pseudocode).let {
nonTrivialVariables.addAll(it.nonTrivialVariables)
valsWithTrivialInitializer.addAll(it.valsWithTrivialInitializer)
}
}
private fun getUpperLevelDeclaredVariables(pseudocode: Pseudocode) = declaredVariablesForDeclaration.getOrPut(pseudocode) {
computeDeclaredVariablesForPseudocode(pseudocode)
}
private fun computeDeclaredVariablesForPseudocode(pseudocode: Pseudocode): VariablesForDeclaration {
val valsWithTrivialInitializer = linkedSetOf<VariableDescriptor>()
val nonTrivialVariables = linkedSetOf<VariableDescriptor>()
for (instruction in pseudocode.instructions) {
if (instruction is VariableDeclarationInstruction) {
val variableDeclarationElement = instruction.variableDeclarationElement
val descriptor =
variableDescriptorForDeclaration(
bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, variableDeclarationElement)
) ?: continue
if (!containsDoWhile && isValWithTrivialInitializer(variableDeclarationElement, descriptor)) {
valsWithTrivialInitializer.add(descriptor)
} else {
nonTrivialVariables.add(descriptor)
}
}
}
return VariablesForDeclaration(
valsWithTrivialInitializer,
nonTrivialVariables
)
}
private fun isValWithTrivialInitializer(variableDeclarationElement: KtDeclaration, descriptor: VariableDescriptor) =
variableDeclarationElement is KtParameter || variableDeclarationElement is KtObjectDeclaration ||
variableDeclarationElement.safeAs<KtVariableDeclaration>()?.isVariableWithTrivialInitializer(descriptor) == true
private fun KtVariableDeclaration.isVariableWithTrivialInitializer(descriptor: VariableDescriptor): Boolean {
if (descriptor.isPropertyWithoutBackingField()) return true
if (isVar) return false
return initializer != null || safeAs<KtProperty>()?.delegate != null || this is KtDestructuringDeclarationEntry
}
private fun VariableDescriptor.isPropertyWithoutBackingField(): Boolean {
if (this !is PropertyDescriptor) return false
return bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, this) != true
}
// variable initializers
private fun computeVariableInitializers(): Map<Instruction, Edges<VariableInitReadOnlyControlFlowInfo>> {
val blockScopeVariableInfo = pseudocodeVariableDataCollector.blockScopeVariableInfo
val resultForValsWithTrivialInitializer = computeInitInfoForTrivialVals()
if (rootVariables.nonTrivialVariables.isEmpty()) return resultForValsWithTrivialInitializer
return pseudocodeVariableDataCollector.collectData(
TraversalOrder.FORWARD,
VariableInitControlFlowInfo()
) { instruction: Instruction, incomingEdgesData: Collection<VariableInitControlFlowInfo> ->
val enterInstructionData =
mergeIncomingEdgesDataForInitializers(
instruction,
incomingEdgesData,
blockScopeVariableInfo
)
val exitInstructionData = addVariableInitStateFromCurrentInstructionIfAny(
instruction, enterInstructionData, blockScopeVariableInfo
)
Edges(enterInstructionData, exitInstructionData)
}.mapValues { (instruction, edges) ->
val trivialEdges = resultForValsWithTrivialInitializer[instruction]!!
Edges(trivialEdges.incoming.replaceDelegate(edges.incoming), trivialEdges.outgoing.replaceDelegate(edges.outgoing))
}
}
private fun computeInitInfoForTrivialVals(): Map<Instruction, Edges<ReadOnlyInitVariableControlFlowInfoImpl>> {
val result = hashMapOf<Instruction, Edges<ReadOnlyInitVariableControlFlowInfoImpl>>()
var declaredSet = ImmutableHashSet.empty<VariableDescriptor>()
var initSet = ImmutableHashSet.empty<VariableDescriptor>()
pseudocode.traverse(TraversalOrder.FORWARD) { instruction ->
val enterState = ReadOnlyInitVariableControlFlowInfoImpl(declaredSet, initSet, null)
when (instruction) {
is VariableDeclarationInstruction ->
extractValWithTrivialInitializer(instruction)?.let { variableDescriptor ->
declaredSet = declaredSet.add(variableDescriptor)
}
is WriteValueInstruction -> {
val variableDescriptor = extractValWithTrivialInitializer(instruction)
if (variableDescriptor != null && instruction.isTrivialInitializer()) {
initSet = initSet.add(variableDescriptor)
}
}
}
val afterState = ReadOnlyInitVariableControlFlowInfoImpl(declaredSet, initSet, null)
result[instruction] = Edges(enterState, afterState)
}
return result
}
private fun WriteValueInstruction.isTrivialInitializer() =
// WriteValueInstruction having KtDeclaration as an element means
// it must be a write happened at the same time when
// the variable (common variable/parameter/object) has been declared
element is KtDeclaration
private inner class ReadOnlyInitVariableControlFlowInfoImpl(
val declaredSet: ImmutableSet<VariableDescriptor>,
val initSet: ImmutableSet<VariableDescriptor>,
private val delegate: VariableInitReadOnlyControlFlowInfo?
) : VariableInitReadOnlyControlFlowInfo {
override fun getOrNull(key: VariableDescriptor): VariableControlFlowState? {
if (key in declaredSet) {
return VariableControlFlowState.create(isInitialized = key in initSet, isDeclared = true)
}
return delegate?.getOrNull(key)
}
override fun checkDefiniteInitializationInWhen(merge: VariableInitReadOnlyControlFlowInfo): Boolean =
delegate?.checkDefiniteInitializationInWhen(merge) ?: false
fun replaceDelegate(newDelegate: VariableInitReadOnlyControlFlowInfo): VariableInitReadOnlyControlFlowInfo =
ReadOnlyInitVariableControlFlowInfoImpl(declaredSet, initSet, newDelegate)
override fun asMap(): ImmutableMap<VariableDescriptor, VariableControlFlowState> {
val initial = delegate?.asMap() ?: ImmutableHashMap.empty()
return declaredSet.fold(initial) { acc, variableDescriptor ->
acc.put(variableDescriptor, getOrNull(variableDescriptor)!!)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ReadOnlyInitVariableControlFlowInfoImpl
if (declaredSet != other.declaredSet) return false
if (initSet != other.initSet) return false
if (delegate != other.delegate) return false
return true
}
override fun hashCode(): Int {
var result = declaredSet.hashCode()
result = 31 * result + initSet.hashCode()
result = 31 * result + (delegate?.hashCode() ?: 0)
return result
}
}
private fun addVariableInitStateFromCurrentInstructionIfAny(
instruction: Instruction,
enterInstructionData: VariableInitControlFlowInfo,
blockScopeVariableInfo: BlockScopeVariableInfo
): VariableInitControlFlowInfo {
if (instruction is MagicInstruction) {
if (instruction.kind === MagicKind.EXHAUSTIVE_WHEN_ELSE) {
return enterInstructionData.iterator().fold(enterInstructionData) { result, (key, value) ->
if (!value.definitelyInitialized()) {
result.put(
key,
VariableControlFlowState.createInitializedExhaustively(value.isDeclared)
)
} else result
}
}
}
if (instruction !is WriteValueInstruction && instruction !is VariableDeclarationInstruction) {
return enterInstructionData
}
val variable =
PseudocodeUtil.extractVariableDescriptorIfAny(instruction, bindingContext)
?.takeIf { it in rootVariables.nonTrivialVariables }
?: return enterInstructionData
var exitInstructionData = enterInstructionData
if (instruction is WriteValueInstruction) {
// if writing to already initialized object
if (!PseudocodeUtil.isThisOrNoDispatchReceiver(instruction, bindingContext)) {
return enterInstructionData
}
val enterInitState = enterInstructionData.getOrNull(variable)
val initializationAtThisElement =
VariableControlFlowState.create(instruction.element is KtProperty, enterInitState)
exitInstructionData = exitInstructionData.put(variable, initializationAtThisElement, enterInitState)
} else {
// instruction instanceof VariableDeclarationInstruction
val enterInitState =
enterInstructionData.getOrNull(variable)
?: getDefaultValueForInitializers(
variable,
instruction,
blockScopeVariableInfo
)
if (!enterInitState.mayBeInitialized() || !enterInitState.isDeclared) {
val variableDeclarationInfo =
VariableControlFlowState.create(enterInitState.initState, isDeclared = true)
exitInstructionData = exitInstructionData.put(variable, variableDeclarationInfo, enterInitState)
}
}
return exitInstructionData
}
// variable use
val variableUseStatusData: Map<Instruction, Edges<VariableUsageReadOnlyControlInfo>>
get() {
val edgesForTrivialVals = computeUseInfoForTrivialVals()
if (rootVariables.nonTrivialVariables.isEmpty()) {
return hashMapOf<Instruction, Edges<VariableUsageReadOnlyControlInfo>>().apply {
pseudocode.traverse(TraversalOrder.FORWARD) { instruction ->
put(instruction, edgesForTrivialVals)
}
}
}
return pseudocodeVariableDataCollector.collectData(
TraversalOrder.BACKWARD,
UsageVariableControlFlowInfo()
) { instruction: Instruction, incomingEdgesData: Collection<UsageVariableControlFlowInfo> ->
val enterResult: UsageVariableControlFlowInfo = if (incomingEdgesData.size == 1) {
incomingEdgesData.single()
} else {
incomingEdgesData.fold(UsageVariableControlFlowInfo()) { result, edgeData ->
edgeData.iterator().fold(result) { subResult, (variableDescriptor, variableUseState) ->
subResult.put(variableDescriptor, variableUseState.merge(subResult.getOrNull(variableDescriptor)))
}
}
}
val variableDescriptor =
PseudocodeUtil.extractVariableDescriptorFromReference(instruction, bindingContext)
?.takeIf { it in rootVariables.nonTrivialVariables }
if (variableDescriptor == null || instruction !is ReadValueInstruction && instruction !is WriteValueInstruction) {
Edges(enterResult, enterResult)
} else {
val exitResult =
if (instruction is ReadValueInstruction) {
enterResult.put(variableDescriptor, VariableUseState.READ)
} else {
var variableUseState: VariableUseState? = enterResult.getOrNull(variableDescriptor)
if (variableUseState == null) {
variableUseState = VariableUseState.UNUSED
}
when (variableUseState) {
VariableUseState.UNUSED, VariableUseState.ONLY_WRITTEN_NEVER_READ ->
enterResult.put(variableDescriptor, VariableUseState.ONLY_WRITTEN_NEVER_READ)
VariableUseState.WRITTEN_AFTER_READ, VariableUseState.READ ->
enterResult.put(variableDescriptor, VariableUseState.WRITTEN_AFTER_READ)
}
}
Edges(enterResult, exitResult)
}
}.mapValues { (_, edges) ->
Edges(
edgesForTrivialVals.incoming.replaceDelegate(edges.incoming),
edgesForTrivialVals.outgoing.replaceDelegate(edges.outgoing)
)
}
}
private fun computeUseInfoForTrivialVals(): Edges<ReadOnlyUseControlFlowInfoImpl> {
val used = hashSetOf<VariableDescriptor>()
pseudocode.traverse(TraversalOrder.FORWARD) { instruction ->
if (instruction is ReadValueInstruction) {
extractValWithTrivialInitializer(instruction)?.let {
used.add(it)
}
}
}
val constantUseInfo = ReadOnlyUseControlFlowInfoImpl(used, null)
return Edges(constantUseInfo, constantUseInfo)
}
private fun extractValWithTrivialInitializer(instruction: Instruction): VariableDescriptor? {
return PseudocodeUtil.extractVariableDescriptorIfAny(instruction, bindingContext)?.takeIf {
it in rootVariables.valsWithTrivialInitializer
}
}
private inner class ReadOnlyUseControlFlowInfoImpl(
val used: Set<VariableDescriptor>,
val delegate: VariableUsageReadOnlyControlInfo?
) : VariableUsageReadOnlyControlInfo {
override fun getOrNull(key: VariableDescriptor): VariableUseState? {
if (key in used) return VariableUseState.READ
return delegate?.getOrNull(key)
}
fun replaceDelegate(newDelegate: VariableUsageReadOnlyControlInfo): VariableUsageReadOnlyControlInfo =
ReadOnlyUseControlFlowInfoImpl(used, newDelegate)
override fun asMap(): ImmutableMap<VariableDescriptor, VariableUseState> {
val initial = delegate?.asMap() ?: ImmutableHashMap.empty()
return used.fold(initial) { acc, variableDescriptor ->
acc.put(variableDescriptor, getOrNull(variableDescriptor)!!)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ReadOnlyUseControlFlowInfoImpl
if (used != other.used) return false
if (delegate != other.delegate) return false
return true
}
override fun hashCode(): Int {
var result = used.hashCode()
result = 31 * result + (delegate?.hashCode() ?: 0)
return result
}
}
companion object {
@JvmStatic
fun getDefaultValueForInitializers(
variable: VariableDescriptor,
instruction: Instruction,
blockScopeVariableInfo: BlockScopeVariableInfo
): VariableControlFlowState {
//todo: think of replacing it with "MapWithDefaultValue"
val declaredIn = blockScopeVariableInfo.declaredIn[variable]
val declaredOutsideThisDeclaration =
declaredIn == null //declared outside this pseudocode
|| declaredIn.blockScopeForContainingDeclaration != instruction.blockScope.blockScopeForContainingDeclaration
return VariableControlFlowState.create(isInitialized = declaredOutsideThisDeclaration)
}
private val EMPTY_INIT_CONTROL_FLOW_INFO = VariableInitControlFlowInfo()
private fun mergeIncomingEdgesDataForInitializers(
instruction: Instruction,
incomingEdgesData: Collection<VariableInitControlFlowInfo>,
blockScopeVariableInfo: BlockScopeVariableInfo
): VariableInitControlFlowInfo {
if (incomingEdgesData.size == 1) return incomingEdgesData.single()
if (incomingEdgesData.isEmpty()) return EMPTY_INIT_CONTROL_FLOW_INFO
val variablesInScope = linkedSetOf<VariableDescriptor>()
for (edgeData in incomingEdgesData) {
variablesInScope.addAll(edgeData.keySet())
}
return variablesInScope.fold(EMPTY_INIT_CONTROL_FLOW_INFO) { result, variable ->
var initState: InitState? = null
var isDeclared = true
for (edgeData in incomingEdgesData) {
val varControlFlowState = edgeData.getOrNull(variable)
?: getDefaultValueForInitializers(
variable,
instruction,
blockScopeVariableInfo
)
initState = initState?.merge(varControlFlowState.initState) ?: varControlFlowState.initState
if (!varControlFlowState.isDeclared) {
isDeclared = false
}
}
if (initState == null) {
throw AssertionError("An empty set of incoming edges data")
}
result.put(variable, VariableControlFlowState.create(initState, isDeclared))
}
}
}
}
@@ -0,0 +1,131 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.cfg.variable
import org.jetbrains.kotlin.cfg.ControlFlowInfo
import org.jetbrains.kotlin.cfg.ReadOnlyControlFlowInfo
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.util.javaslang.ImmutableHashMap
import org.jetbrains.kotlin.util.javaslang.ImmutableMap
import org.jetbrains.kotlin.util.javaslang.component1
import org.jetbrains.kotlin.util.javaslang.component2
typealias VariableUsageReadOnlyControlInfo = ReadOnlyControlFlowInfo<VariableDescriptor, VariableUseState>
typealias VariableUsageControlFlowInfo<S, D> = ControlFlowInfo<S, VariableDescriptor, D>
interface VariableInitReadOnlyControlFlowInfo :
ReadOnlyControlFlowInfo<VariableDescriptor, VariableControlFlowState> {
fun checkDefiniteInitializationInWhen(merge: VariableInitReadOnlyControlFlowInfo): Boolean
}
class VariableInitControlFlowInfo(map: ImmutableMap<VariableDescriptor, VariableControlFlowState> = ImmutableHashMap.empty()) :
VariableUsageControlFlowInfo<VariableInitControlFlowInfo, VariableControlFlowState>(map),
VariableInitReadOnlyControlFlowInfo {
override fun copy(newMap: ImmutableMap<VariableDescriptor, VariableControlFlowState>) =
VariableInitControlFlowInfo(newMap)
// this = output of EXHAUSTIVE_WHEN_ELSE instruction
// merge = input of MergeInstruction
// returns true if definite initialization in when happens here
override fun checkDefiniteInitializationInWhen(merge: VariableInitReadOnlyControlFlowInfo): Boolean {
for ((key, value) in iterator()) {
if (value.initState == InitState.INITIALIZED_EXHAUSTIVELY &&
merge.getOrNull(key)?.initState == InitState.INITIALIZED
) {
return true
}
}
return false
}
}
class UsageVariableControlFlowInfo(map: ImmutableMap<VariableDescriptor, VariableUseState> = ImmutableHashMap.empty()) :
VariableUsageControlFlowInfo<UsageVariableControlFlowInfo, VariableUseState>(map),
VariableUsageReadOnlyControlInfo {
override fun copy(newMap: ImmutableMap<VariableDescriptor, VariableUseState>) =
UsageVariableControlFlowInfo(newMap)
}
enum class InitState(private val s: String) {
// Definitely initialized
INITIALIZED("I"),
// Fake initializer in else branch of "exhaustive when without else", see MagicKind.EXHAUSTIVE_WHEN_ELSE
INITIALIZED_EXHAUSTIVELY("IE"),
// Initialized in some branches, not initialized in other branches
UNKNOWN("I?"),
// Definitely not initialized
NOT_INITIALIZED("");
fun merge(other: InitState): InitState {
// X merge X = X
// X merge IE = IE merge X = X
// else X merge Y = I?
if (this == other || other == INITIALIZED_EXHAUSTIVELY) return this
if (this == INITIALIZED_EXHAUSTIVELY) return other
return UNKNOWN
}
override fun toString() = s
}
class VariableControlFlowState private constructor(val initState: InitState, val isDeclared: Boolean) {
fun definitelyInitialized(): Boolean = initState == InitState.INITIALIZED
fun mayBeInitialized(): Boolean = initState != InitState.NOT_INITIALIZED
override fun toString(): String {
if (initState == InitState.NOT_INITIALIZED && !isDeclared) return "-"
return "$initState${if (isDeclared) "D" else ""}"
}
companion object {
private val VS_IT = VariableControlFlowState(InitState.INITIALIZED, true)
private val VS_IF = VariableControlFlowState(InitState.INITIALIZED, false)
private val VS_ET = VariableControlFlowState(InitState.INITIALIZED_EXHAUSTIVELY, true)
private val VS_EF = VariableControlFlowState(InitState.INITIALIZED_EXHAUSTIVELY, false)
private val VS_UT = VariableControlFlowState(InitState.UNKNOWN, true)
private val VS_UF = VariableControlFlowState(InitState.UNKNOWN, false)
private val VS_NT = VariableControlFlowState(InitState.NOT_INITIALIZED, true)
private val VS_NF = VariableControlFlowState(InitState.NOT_INITIALIZED, false)
fun create(initState: InitState, isDeclared: Boolean): VariableControlFlowState =
when (initState) {
InitState.INITIALIZED -> if (isDeclared) VS_IT else VS_IF
InitState.INITIALIZED_EXHAUSTIVELY -> if (isDeclared) VS_ET else VS_EF
InitState.UNKNOWN -> if (isDeclared) VS_UT else VS_UF
InitState.NOT_INITIALIZED -> if (isDeclared) VS_NT else VS_NF
}
fun createInitializedExhaustively(isDeclared: Boolean): VariableControlFlowState =
create(InitState.INITIALIZED_EXHAUSTIVELY, isDeclared)
fun create(isInitialized: Boolean, isDeclared: Boolean = false): VariableControlFlowState =
create(if (isInitialized) InitState.INITIALIZED else InitState.NOT_INITIALIZED, isDeclared)
fun create(isDeclaredHere: Boolean, mergedEdgesData: VariableControlFlowState?): VariableControlFlowState =
create(true, isDeclaredHere || mergedEdgesData != null && mergedEdgesData.isDeclared)
}
}
enum class VariableUseState(private val priority: Int) {
READ(3),
WRITTEN_AFTER_READ(2),
ONLY_WRITTEN_NEVER_READ(1),
UNUSED(0);
fun merge(variableUseState: VariableUseState?): VariableUseState {
if (variableUseState == null || priority > variableUseState.priority) return this
return variableUseState
}
companion object {
@JvmStatic
fun isUsed(variableUseState: VariableUseState?): Boolean = variableUseState != null && variableUseState != UNUSED
}
}