[JS IR] Implemented a set of JS code checks before klib serialization

The diagnostics cannot be implemented with the FIR frontend checker
because it requires constant evaluation over FIR.
Therefore, the diagnostics are implemented as a set of klib checks over IR.

For the diagnostics, the js() call argument must be
evaluated and inlined as IrConst<String> into IR
in the same way as const val initializers and annotation arguments.

^KT-59388 Fixed
^KT-59399 Fixed
^KT-62425 Fixed
This commit is contained in:
Alexander Korepanov
2023-09-21 20:59:17 +02:00
committed by Space Team
parent 71eaf651e8
commit 629e0628d6
13 changed files with 275 additions and 28 deletions
@@ -6,6 +6,8 @@ plugins {
dependencies {
compileOnly(project(":compiler:ir.tree"))
compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false }
implementation(project(":core:compiler.common.js"))
}
optInToUnsafeDuringIrConstructionAPI()
@@ -42,14 +42,14 @@ internal abstract class IrConstExpressionTransformer(
override fun visitClass(declaration: IrClass, data: Data): IrStatement {
if (declaration.kind == ClassKind.ANNOTATION_CLASS) {
return super.visitClass(declaration, data.copy(inAnnotation = true))
return super.visitClass(declaration, data.copy(inConstantExpression = true))
}
return super.visitClass(declaration, data)
}
override fun visitCall(expression: IrCall, data: Data): IrElement {
if (expression.canBeInterpreted()) {
return expression.interpret(failAsError = data.inAnnotation)
return expression.interpret(failAsError = data.inConstantExpression)
}
return super.visitCall(expression, data)
}
@@ -68,7 +68,7 @@ internal abstract class IrConstExpressionTransformer(
override fun visitGetField(expression: IrGetField, data: Data): IrExpression {
if (expression.canBeInterpreted()) {
return expression.interpret(failAsError = data.inAnnotation)
return expression.interpret(failAsError = data.inConstantExpression)
}
return super.visitGetField(expression, data)
}
@@ -78,7 +78,7 @@ internal abstract class IrConstExpressionTransformer(
this.startOffset, this.endOffset, expression.type, listOf(this@wrapInStringConcat)
)
fun IrExpression.wrapInToStringConcatAndInterpret(): IrExpression = wrapInStringConcat().interpret(failAsError = data.inAnnotation)
fun IrExpression.wrapInToStringConcatAndInterpret(): IrExpression = wrapInStringConcat().interpret(failAsError = data.inConstantExpression)
fun IrExpression.getConstStringOrEmpty(): String = if (this is IrConst<*>) value.toString() else ""
// If we have some complex expression in arguments (like some `IrComposite`) we will skip it,
@@ -11,16 +11,19 @@ import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.interpreter.IrInterpreter
import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode
import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterChecker
import org.jetbrains.kotlin.ir.interpreter.isConst
import org.jetbrains.kotlin.ir.interpreter.property
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.name.JsStandardClassIds
/**
* This transformer will visit all expressions and will evaluate only those that are necessary. By "necessary" we mean expressions
* that are used in `const val` and inside annotations.
* This transformer will visit all expressions and will evaluate only those that are necessary.
* By "necessary" we mean expressions that are used in `const val`, inside annotations and js() call arguments.
*/
internal class IrConstOnlyNecessaryTransformer(
interpreter: IrInterpreter,
@@ -35,10 +38,13 @@ internal class IrConstOnlyNecessaryTransformer(
) : IrConstExpressionTransformer(
interpreter, irFile, mode, checker, evaluatedConstTracker, inlineConstTracker, onWarning, onError, suppressExceptions
) {
private val jsCodeFqName = JsStandardClassIds.Callables.JsCode.asSingleFqName()
override fun visitCall(expression: IrCall, data: Data): IrElement {
val isConstGetter = expression.symbol.owner.property.isConst
if (!data.inAnnotation && !isConstGetter) {
expression.transformChildren(this, data)
val isJsCodeCall = expression.symbol.owner.fqNameWhenAvailable == jsCodeFqName
if (isJsCodeCall || (!data.inConstantExpression && !isConstGetter)) {
expression.transformChildren(this, data.copy(inConstantExpression = data.inConstantExpression || isJsCodeCall))
return expression
}
return super.visitCall(expression, data)
@@ -46,12 +52,12 @@ internal class IrConstOnlyNecessaryTransformer(
override fun visitGetField(expression: IrGetField, data: Data): IrExpression {
val isConst = expression.symbol.owner.property.isConst
if (!data.inAnnotation && !isConst) return expression
if (!data.inConstantExpression && !isConst) return expression
return super.visitGetField(expression, data)
}
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Data): IrExpression {
if (!data.inAnnotation) {
if (!data.inConstantExpression) {
expression.transformChildren(this, data)
return expression
}
@@ -99,7 +99,7 @@ internal abstract class IrConstTransformer(
private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit,
private val suppressExceptions: Boolean,
) : IrElementTransformer<IrConstTransformer.Data> {
internal data class Data(val inAnnotation: Boolean = false)
internal data class Data(val inConstantExpression: Boolean = false)
private fun IrExpression.warningIfError(original: IrExpression): IrExpression {
if (this is IrErrorExpression) {