[IR] Create common interface for all interpreter checkers

This commit is contained in:
Ivan Kylchik
2023-04-28 11:21:34 +02:00
committed by Space Team
parent 82a111250b
commit c8371a339f
4 changed files with 99 additions and 84 deletions
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2023 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.ir.interpreter.checker
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
interface IrInterpreterChecker : IrElementVisitor<Boolean, IrInterpreterCheckerData> {
val mode: EvaluationMode
}
class IrInterpreterCheckerData(
val interpreterConfiguration: IrInterpreterConfiguration
)
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.interpreter.accessesTopLevelOrObjectField
import org.jetbrains.kotlin.ir.interpreter.fqName
import org.jetbrains.kotlin.ir.interpreter.isAccessToNotNullableObject
@@ -17,12 +16,10 @@ import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
class IrCompileTimeChecker(
private val mode: EvaluationMode,
private val interpreterConfiguration: IrInterpreterConfiguration,
) : IrElementVisitor<Boolean, Nothing?> {
class IrInterpreterCommonChecker(
override val mode: EvaluationMode
) : IrInterpreterChecker {
private val visitedStack = mutableListOf<IrElement>()
private inline fun IrElement.asVisited(crossinline block: () -> Boolean): Boolean {
@@ -32,35 +29,35 @@ class IrCompileTimeChecker(
return result
}
override fun visitElement(element: IrElement, data: Nothing?) = false
override fun visitElement(element: IrElement, data: IrInterpreterCheckerData) = false
private fun IrDeclarationParent.getInnerDeclarations(): List<IrStatement> {
return (this as? IrDeclarationContainer)?.declarations ?: (this as? IrStatementContainer)?.statements ?: emptyList()
}
private fun visitStatements(statements: List<IrStatement>): Boolean {
return statements.all { it.accept(this, null) }
private fun visitStatements(statements: List<IrStatement>, data: IrInterpreterCheckerData): Boolean {
return statements.all { it.accept(this, data) }
}
private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean {
private fun visitConstructor(expression: IrFunctionAccessExpression, data: IrInterpreterCheckerData): Boolean {
val constructor = expression.symbol.owner
if (!mode.canEvaluateFunction(constructor)) return false
if (!visitValueArguments(expression, null)) return false
return constructor.visitBodyIfNeeded() &&
constructor.parentAsClass.declarations.filterIsInstance<IrAnonymousInitializer>().all { it.accept(this, null) }
if (!visitValueArguments(expression, data)) return false
return visitBodyIfNeeded(constructor, data) &&
constructor.parentAsClass.declarations.filterIsInstance<IrAnonymousInitializer>().all { it.accept(this, data) }
}
private fun IrFunction.visitBodyIfNeeded(): Boolean {
if (!mode.mustCheckBodyOf(this)) return true
return this.asVisited { this.body?.accept(this@IrCompileTimeChecker, null) ?: true }
private fun visitBodyIfNeeded(irFunction: IrFunction, data: IrInterpreterCheckerData): Boolean {
if (!mode.mustCheckBodyOf(irFunction)) return true
return irFunction.asVisited { irFunction.body?.accept(this@IrInterpreterCommonChecker, data) ?: true }
}
private fun IrCall.isGetterToConstVal(): Boolean {
return symbol.owner.correspondingPropertySymbol?.owner?.isConst == true
}
override fun visitCall(expression: IrCall, data: Nothing?): Boolean {
override fun visitCall(expression: IrCall, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false
val owner = expression.symbol.owner
@@ -69,7 +66,7 @@ class IrCompileTimeChecker(
// We disable `toFloat` folding on K/JS till `toFloat` is fixed (KT-35422)
// This check must be placed here instead of CallInterceptor because we still
// want to evaluate (1) `const val` expressions and (2) values in annotations.
if (owner.name.asString() == "toFloat" && interpreterConfiguration.treatFloatInSpecialWay) {
if (owner.name.asString() == "toFloat" && data.interpreterConfiguration.treatFloatInSpecialWay) {
return super.visitCall(expression, data)
}
@@ -77,33 +74,33 @@ class IrCompileTimeChecker(
return expression.isGetterToConstVal()
}
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!visitValueArguments(expression, null)) return false
val bodyComputable = owner.visitBodyIfNeeded()
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
if (!visitValueArguments(expression, data)) return false
val bodyComputable = visitBodyIfNeeded(owner, data)
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
}
override fun visitVariable(declaration: IrVariable, data: Nothing?): Boolean {
override fun visitVariable(declaration: IrVariable, data: IrInterpreterCheckerData): Boolean {
return declaration.initializer?.accept(this, data) ?: true
}
private fun visitValueArguments(expression: IrFunctionAccessExpression, data: Nothing?): Boolean {
private fun visitValueArguments(expression: IrFunctionAccessExpression, data: IrInterpreterCheckerData): Boolean {
return (0 until expression.valueArgumentsCount)
.map { expression.getValueArgument(it) }
.none { it?.accept(this, data) == false }
}
override fun visitBody(body: IrBody, data: Nothing?): Boolean {
return visitStatements(body.statements)
override fun visitBody(body: IrBody, data: IrInterpreterCheckerData): Boolean {
return visitStatements(body.statements, data)
}
// We need this separate method to explicitly indicate that IrExpressionBody can be interpreted in any evaluation mode
override fun visitExpressionBody(body: IrExpressionBody, data: Nothing?): Boolean {
override fun visitExpressionBody(body: IrExpressionBody, data: IrInterpreterCheckerData): Boolean {
return body.expression.accept(this, data)
}
override fun visitBlock(expression: IrBlock, data: Nothing?): Boolean {
override fun visitBlock(expression: IrBlock, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateBlock(expression)) return false
// `IrReturnableBlock` will be created from IrCall after inline. We should do basically the same check as for IrCall.
@@ -112,20 +109,20 @@ class IrCompileTimeChecker(
if (inlinedBlock != null) return inlinedBlock.inlineCall.accept(this, data)
}
return visitStatements(expression.statements)
return visitStatements(expression.statements, data)
}
override fun visitComposite(expression: IrComposite, data: Nothing?): Boolean {
override fun visitComposite(expression: IrComposite, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateComposite(expression)) return false
return visitStatements(expression.statements)
return visitStatements(expression.statements, data)
}
override fun visitSyntheticBody(body: IrSyntheticBody, data: Nothing?): Boolean {
override fun visitSyntheticBody(body: IrSyntheticBody, data: IrInterpreterCheckerData): Boolean {
return body.kind == IrSyntheticBodyKind.ENUM_VALUES || body.kind == IrSyntheticBodyKind.ENUM_VALUEOF
}
override fun visitConst(expression: IrConst<*>, data: Nothing?): Boolean {
override fun visitConst(expression: IrConst<*>, data: IrInterpreterCheckerData): Boolean {
if (expression.type.getUnsignedType() != null) {
val constructor = expression.type.classOrNull?.owner?.constructors?.singleOrNull() ?: return false
return mode.canEvaluateFunction(constructor)
@@ -133,15 +130,15 @@ class IrCompileTimeChecker(
return true
}
override fun visitVararg(expression: IrVararg, data: Nothing?): Boolean {
override fun visitVararg(expression: IrVararg, data: IrInterpreterCheckerData): Boolean {
return expression.elements.any { it.accept(this, data) }
}
override fun visitSpreadElement(spread: IrSpreadElement, data: Nothing?): Boolean {
override fun visitSpreadElement(spread: IrSpreadElement, data: IrInterpreterCheckerData): Boolean {
return spread.expression.accept(this, data)
}
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean {
override fun visitStringConcatenation(expression: IrStringConcatenation, data: IrInterpreterCheckerData): Boolean {
return expression.arguments.all { arg ->
when (arg) {
is IrGetObjectValue -> {
@@ -149,7 +146,7 @@ class IrCompileTimeChecker(
.filterIsInstance<IrSimpleFunction>()
.single { it.name.asString() == "toString" && it.valueParameters.isEmpty() && it.extensionReceiverParameter == null }
mode.canEvaluateFunction(toString) && toString.visitBodyIfNeeded()
mode.canEvaluateFunction(toString) && visitBodyIfNeeded(toString, data)
}
else -> arg.accept(this, data)
@@ -157,12 +154,12 @@ class IrCompileTimeChecker(
}
}
override fun visitGetObjectValue(expression: IrGetObjectValue, data: Nothing?): Boolean {
override fun visitGetObjectValue(expression: IrGetObjectValue, data: IrInterpreterCheckerData): Boolean {
// to get object value we need nothing, but it will contain only fields with compile time annotation
return true
}
override fun visitGetEnumValue(expression: IrGetEnumValue, data: Nothing?): Boolean {
override fun visitGetEnumValue(expression: IrGetEnumValue, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateEnumValue(expression)) return false
// we want to avoid recursion in cases like "enum class E(val srt: String) { OK(OK.name) }"
@@ -172,15 +169,15 @@ class IrCompileTimeChecker(
}
}
override fun visitGetValue(expression: IrGetValue, data: Nothing?): Boolean {
override fun visitGetValue(expression: IrGetValue, data: IrInterpreterCheckerData): Boolean {
return visitedStack.contains(expression.symbol.owner.parent)
}
override fun visitSetValue(expression: IrSetValue, data: Nothing?): Boolean {
override fun visitSetValue(expression: IrSetValue, data: IrInterpreterCheckerData): Boolean {
return expression.value.accept(this, data)
}
override fun visitGetField(expression: IrGetField, data: Nothing?): Boolean {
override fun visitGetField(expression: IrGetField, data: IrInterpreterCheckerData): Boolean {
val owner = expression.symbol.owner
val property = owner.correspondingPropertySymbol?.owner
val fqName = owner.fqName
@@ -194,11 +191,11 @@ class IrCompileTimeChecker(
// its type is flexible (so its not primitive) and there is no initializer at backing field
fqName == "java.lang.Boolean.FALSE" || fqName == "java.lang.Boolean.TRUE" -> true
isJavaStaticWithPrimitiveOrString() -> owner.initializer?.accept(this, data) == true
expression.receiver == null -> property?.isConst == true && owner.initializer?.accept(this, null) == true
expression.receiver == null -> property?.isConst == true && owner.initializer?.accept(this, data) == true
owner.origin == IrDeclarationOrigin.PROPERTY_BACKING_FIELD && property?.isConst == true -> {
val receiverComputable = (expression.receiver?.accept(this, null) ?: true)
val receiverComputable = (expression.receiver?.accept(this, data) ?: true)
|| expression.isAccessToNotNullableObject()
val initializerComputable = owner.initializer?.accept(this, null) ?: false
val initializerComputable = owner.initializer?.accept(this, data) ?: false
receiverComputable && initializerComputable
}
else -> {
@@ -210,7 +207,7 @@ class IrCompileTimeChecker(
}
}
override fun visitSetField(expression: IrSetField, data: Nothing?): Boolean {
override fun visitSetField(expression: IrSetField, data: IrInterpreterCheckerData): Boolean {
if (expression.accessesTopLevelOrObjectField()) return false
//todo check receiver?
val property = expression.symbol.owner.correspondingPropertySymbol?.owner
@@ -219,20 +216,20 @@ class IrCompileTimeChecker(
return visitedStack.contains(setter) && expression.value.accept(this, data)
}
override fun visitConstructorCall(expression: IrConstructorCall, data: Nothing?): Boolean {
return visitConstructor(expression)
override fun visitConstructorCall(expression: IrConstructorCall, data: IrInterpreterCheckerData): Boolean {
return visitConstructor(expression, data)
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: Nothing?): Boolean {
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: IrInterpreterCheckerData): Boolean {
if (expression.symbol.owner.returnType.isAny()) return true
return visitConstructor(expression)
return visitConstructor(expression, data)
}
override fun visitEnumConstructorCall(expression: IrEnumConstructorCall, data: Nothing?): Boolean {
return visitConstructor(expression)
override fun visitEnumConstructorCall(expression: IrEnumConstructorCall, data: IrInterpreterCheckerData): Boolean {
return visitConstructor(expression, data)
}
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, data: Nothing?): Boolean {
override fun visitInstanceInitializerCall(expression: IrInstanceInitializerCall, data: IrInterpreterCheckerData): Boolean {
val irClass = expression.classSymbol.owner
val classProperties = irClass.declarations.filterIsInstance<IrProperty>()
val anonymousInitializer = irClass.declarations.filterIsInstance<IrAnonymousInitializer>().filter { !it.isStatic }
@@ -244,27 +241,27 @@ class IrCompileTimeChecker(
}
}
override fun visitFunctionReference(expression: IrFunctionReference, data: Nothing?): Boolean {
override fun visitFunctionReference(expression: IrFunctionReference, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateCallableReference(expression)) return false
val owner = expression.symbol.owner
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
if (!mode.canEvaluateFunction(owner)) return false
val bodyComputable = owner.visitBodyIfNeeded()
val bodyComputable = visitBodyIfNeeded(owner, data)
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
}
override fun visitFunctionExpression(expression: IrFunctionExpression, data: Nothing?): Boolean {
override fun visitFunctionExpression(expression: IrFunctionExpression, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateFunctionExpression(expression)) return false
val body = expression.function.body ?: return false
return expression.function.asVisited { body.accept(this, data) }
}
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: Nothing?): Boolean {
override fun visitTypeOperator(expression: IrTypeOperatorCall, data: IrInterpreterCheckerData): Boolean {
return when (expression.operator) {
IrTypeOperator.INSTANCEOF, IrTypeOperator.NOT_INSTANCEOF,
IrTypeOperator.IMPLICIT_COERCION_TO_UNIT, IrTypeOperator.IMPLICIT_NOTNULL, IrTypeOperator.SAM_CONVERSION,
@@ -277,29 +274,29 @@ class IrCompileTimeChecker(
}
}
override fun visitWhen(expression: IrWhen, data: Nothing?): Boolean {
override fun visitWhen(expression: IrWhen, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false
return expression.branches.all { it.accept(this, data) }
}
override fun visitBranch(branch: IrBranch, data: Nothing?): Boolean {
override fun visitBranch(branch: IrBranch, data: IrInterpreterCheckerData): Boolean {
return branch.condition.accept(this, data) && branch.result.accept(this, data)
}
override fun visitWhileLoop(loop: IrWhileLoop, data: Nothing?): Boolean {
override fun visitWhileLoop(loop: IrWhileLoop, data: IrInterpreterCheckerData): Boolean {
return loop.asVisited {
loop.condition.accept(this, data) && (loop.body?.accept(this, data) ?: true)
}
}
override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: Nothing?): Boolean {
override fun visitDoWhileLoop(loop: IrDoWhileLoop, data: IrInterpreterCheckerData): Boolean {
return loop.asVisited {
loop.condition.accept(this, data) && (loop.body?.accept(this, data) ?: true)
}
}
override fun visitTry(aTry: IrTry, data: Nothing?): Boolean {
override fun visitTry(aTry: IrTry, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(aTry)) return false
if (!aTry.tryResult.accept(this, data)) return false
@@ -307,32 +304,32 @@ class IrCompileTimeChecker(
return aTry.catches.all { it.result.accept(this, data) }
}
override fun visitBreak(jump: IrBreak, data: Nothing?): Boolean = visitedStack.contains(jump.loop)
override fun visitBreak(jump: IrBreak, data: IrInterpreterCheckerData): Boolean = visitedStack.contains(jump.loop)
override fun visitContinue(jump: IrContinue, data: Nothing?): Boolean = visitedStack.contains(jump.loop)
override fun visitContinue(jump: IrContinue, data: IrInterpreterCheckerData): Boolean = visitedStack.contains(jump.loop)
override fun visitReturn(expression: IrReturn, data: Nothing?): Boolean {
override fun visitReturn(expression: IrReturn, data: IrInterpreterCheckerData): Boolean {
if (!visitedStack.contains(expression.returnTargetSymbol.owner)) return false
return expression.value.accept(this, data)
}
override fun visitThrow(expression: IrThrow, data: Nothing?): Boolean {
override fun visitThrow(expression: IrThrow, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateExpression(expression)) return false
return expression.value.accept(this, data)
}
override fun visitPropertyReference(expression: IrPropertyReference, data: Nothing?): Boolean {
override fun visitPropertyReference(expression: IrPropertyReference, data: IrInterpreterCheckerData): Boolean {
if (!mode.canEvaluateCallableReference(expression)) return false
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, data) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, data) ?: true
val getterIsComputable = expression.getter?.let { mode.canEvaluateFunction(it.owner) } ?: true
return dispatchReceiverComputable && extensionReceiverComputable && getterIsComputable
}
override fun visitClassReference(expression: IrClassReference, data: Nothing?): Boolean {
override fun visitClassReference(expression: IrClassReference, data: IrInterpreterCheckerData): Boolean {
return mode.canEvaluateClassReference(expression)
}
}
@@ -8,11 +8,10 @@ package org.jetbrains.kotlin.ir.interpreter.checker
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
class IrCompileTimeNameChecker(
private val mode: EvaluationMode
) : IrElementVisitor<Boolean, Nothing?> {
class IrInterpreterNameChecker(
override val mode: EvaluationMode
) : IrInterpreterChecker {
private fun IrCall.isIntrinsicConstEvaluationNameProperty(): Boolean {
val owner = this.symbol.owner
if (owner.extensionReceiverParameter != null || owner.valueParameters.isNotEmpty()) return false
@@ -20,9 +19,9 @@ class IrCompileTimeNameChecker(
return mode.canEvaluateFunction(owner) && property.name.asString() == "name"
}
override fun visitElement(element: IrElement, data: Nothing?) = false
override fun visitElement(element: IrElement, data: IrInterpreterCheckerData) = false
override fun visitCall(expression: IrCall, data: Nothing?): Boolean {
override fun visitCall(expression: IrCall, data: IrInterpreterCheckerData): Boolean {
if (!expression.isIntrinsicConstEvaluationNameProperty()) return false
return when (val receiver = expression.dispatchReceiver) {
is IrCallableReference<*> -> (receiver.dispatchReceiver == null || receiver.dispatchReceiver is IrGetObjectValue) && receiver.extensionReceiver == null
@@ -31,7 +30,7 @@ class IrCompileTimeNameChecker(
}
}
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean {
override fun visitStringConcatenation(expression: IrStringConcatenation, data: IrInterpreterCheckerData): Boolean {
val possibleNameCall = expression.arguments.singleOrNull() as? IrCall ?: return false
return possibleNameCall.accept(this, data)
}
@@ -16,9 +16,7 @@ import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.interpreter.checker.IrCompileTimeChecker
import org.jetbrains.kotlin.ir.interpreter.checker.IrCompileTimeNameChecker
import org.jetbrains.kotlin.ir.interpreter.checker.*
import org.jetbrains.kotlin.ir.interpreter.toConstantValue
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
@@ -56,6 +54,11 @@ internal abstract class IrConstTransformer(
private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
private val suppressExceptions: Boolean,
) : IrElementTransformer<Nothing?> {
private val checkers = setOf(
IrInterpreterCommonChecker(mode),
IrInterpreterNameChecker(mode)
)
private fun IrExpression.warningIfError(original: IrExpression): IrExpression {
if (this is IrErrorExpression) {
onWarning(irFile, original, this)
@@ -80,8 +83,7 @@ internal abstract class IrConstTransformer(
configuration: IrInterpreterConfiguration = interpreter.environment.configuration
): Boolean {
return try {
this.accept(IrCompileTimeChecker(mode, configuration), null) ||
this.accept(IrCompileTimeNameChecker(mode), null)
checkers.any { this.accept(it, IrInterpreterCheckerData(configuration)) }
} catch (e: Throwable) {
if (suppressExceptions) {
return false