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:
committed by
Alexander Udalov
parent
c744515832
commit
2e2caae05c
@@ -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()
|
||||
}
|
||||
+1309
File diff suppressed because it is too large
Load Diff
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;
|
||||
}
|
||||
}
|
||||
+433
@@ -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) }
|
||||
+35
@@ -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
|
||||
}
|
||||
}
|
||||
+38
@@ -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
|
||||
}
|
||||
+64
@@ -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()
|
||||
}
|
||||
+118
@@ -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)
|
||||
}
|
||||
}
|
||||
+73
@@ -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)
|
||||
}
|
||||
+32
@@ -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)
|
||||
}
|
||||
+23
@@ -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
|
||||
}
|
||||
+28
@@ -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(), " ") ?: ""
|
||||
}
|
||||
+25
@@ -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>
|
||||
}
|
||||
+24
@@ -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?
|
||||
}
|
||||
+43
@@ -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)
|
||||
}
|
||||
+129
@@ -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)
|
||||
}
|
||||
+171
@@ -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))
|
||||
}
|
||||
+44
@@ -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)
|
||||
}
|
||||
+72
@@ -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)
|
||||
}
|
||||
+21
@@ -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
|
||||
+80
@@ -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)
|
||||
}
|
||||
+41
@@ -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)
|
||||
}
|
||||
+50
@@ -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
|
||||
}
|
||||
+45
@@ -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)
|
||||
}
|
||||
+38
@@ -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)
|
||||
}
|
||||
+40
@@ -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)})"
|
||||
}
|
||||
+58
@@ -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)
|
||||
}
|
||||
+39
@@ -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)})"
|
||||
}
|
||||
+40
@@ -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)
|
||||
}
|
||||
+49
@@ -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)
|
||||
}
|
||||
+45
@@ -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)
|
||||
}
|
||||
+49
@@ -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
|
||||
+92
@@ -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)
|
||||
}
|
||||
}
|
||||
+470
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+131
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user