Properly verify args in string concatenation expression for interpreter

If string concatenation contains object value, then this argument
will not have explicit toString call. We must find and check toString
method manually.

#KT-54615
This commit is contained in:
Ivan Kylchik
2022-10-21 15:44:38 +02:00
committed by Space Team
parent c813e03c1e
commit 5b16b2c71e
13 changed files with 139 additions and 9 deletions
@@ -20495,6 +20495,12 @@ public class DiagnosisCompilerTestFE10TestdataTestGenerated extends AbstractDiag
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
}
@Test
@TestMetadata("stringConcatenationWithObject.kt")
public void testStringConcatenationWithObject() throws Exception {
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
}
@Test
@TestMetadata("types.kt")
public void testTypes() throws Exception {
@@ -20495,6 +20495,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
}
@Test
@TestMetadata("stringConcatenationWithObject.kt")
public void testStringConcatenationWithObject() throws Exception {
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
}
@Test
@TestMetadata("types.kt")
public void testTypes() throws Exception {
@@ -20495,6 +20495,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
}
@Test
@TestMetadata("stringConcatenationWithObject.kt")
public void testStringConcatenationWithObject() throws Exception {
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
}
@Test
@TestMetadata("types.kt")
public void testTypes() throws Exception {
@@ -66,8 +66,12 @@ internal fun checkConstantArguments(
return checkConstantArguments(expression.compareToCall, session)
}
expression is FirStringConcatenationCall || expression is FirEqualityOperatorCall -> {
for (exp in (expression as FirCall).arguments)
for (exp in (expression as FirCall).arguments) {
if (exp is FirResolvedQualifier) {
return ConstantArgumentKind.NOT_CONST
}
checkConstantArguments(exp, session).let { return it }
}
}
expression is FirGetClassCall -> {
var coneType = (expression as? FirCall)?.argument?.typeRef?.coneType
@@ -17033,6 +17033,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/evaluate/simpleCallBinary.kt");
}
@Test
@TestMetadata("stringConcatenationWithObject.kt")
public void testStringConcatenationWithObject() throws Exception {
runTest("compiler/testData/codegen/box/evaluate/stringConcatenationWithObject.kt");
}
@Test
@TestMetadata("thisPlusString.kt")
public void testThisPlusString() throws Exception {
@@ -103,7 +103,7 @@ enum class EvaluationMode(protected val mustCheckBody: Boolean) {
abstract fun canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall? = null): Boolean
abstract fun canEvaluateReference(reference: IrCallableReference<*>, context: IrCall? = null): Boolean
fun canEvaluateBody(function: IrFunction): Boolean {
fun mustCheckBodyOf(function: IrFunction): Boolean {
if (function is IrSimpleFunction && function.correspondingPropertySymbol != null) return true
return (mustCheckBody || function.isLocal) && !function.isContract() && !function.isMarkedAsEvaluateIntrinsic()
}
@@ -54,11 +54,14 @@ class IrCompileTimeChecker(
private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean {
return when {
!visitValueParameters(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false
mode.canEvaluateBody(expression.symbol.owner) -> expression.symbol.owner.body?.accept(this, null) != false
else -> true
else -> expression.symbol.owner.visitBodyIfNeeded()
}
}
private fun IrFunction.visitBodyIfNeeded(): Boolean {
return this.asVisited { !mode.mustCheckBodyOf(this) || (this.body?.accept(this@IrCompileTimeChecker, null) ?: true) }
}
override fun visitCall(expression: IrCall, data: Nothing?): Boolean {
val owner = expression.symbol.owner
if (!mode.canEvaluateFunction(owner, expression)) return false
@@ -67,7 +70,7 @@ class IrCompileTimeChecker(
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!visitValueParameters(expression, null)) return@saveContext false
val bodyComputable = owner.asVisited { if (mode.canEvaluateBody(owner)) owner.body?.accept(this, null) ?: true else true }
val bodyComputable = owner.visitBodyIfNeeded()
return@saveContext dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
}
}
@@ -126,7 +129,19 @@ class IrCompileTimeChecker(
}
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean {
return expression.arguments.all { it.accept(this, data) }
return expression.arguments.all { arg ->
when (arg) {
is IrGetObjectValue -> {
val toString = arg.symbol.owner.declarations
.filterIsInstance<IrSimpleFunction>()
.single { it.name.asString() == "toString" && it.valueParameters.isEmpty() }
mode.canEvaluateFunction(toString, null) && toString.visitBodyIfNeeded()
}
else -> arg.accept(this, data)
}
}
}
override fun visitGetObjectValue(expression: IrGetObjectValue, data: Nothing?): Boolean {
@@ -218,7 +233,7 @@ class IrCompileTimeChecker(
if (!mode.canEvaluateFunction(owner, contextExpression)) return false
val bodyComputable = owner.asVisited { if (mode.canEvaluateBody(owner)) owner.body?.accept(this, null) ?: true else true }
val bodyComputable = owner.visitBodyIfNeeded()
return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
}
@@ -55,8 +55,10 @@ class IrConstTransformer(
val result = try {
interpreter.interpret(this, irFile)
} catch (e: Throwable) {
if (!suppressExceptions) throw AssertionError("Error occurred while optimizing an expression:\n${this.dump()}", e)
return IrErrorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, this.type, e.message.toString()).warningIfError(this)
if (suppressExceptions) {
return this
}
throw AssertionError("Error occurred while optimizing an expression:\n${this.dump()}", e)
}
return if (failAsError) result.reportIfError(this) else result.warningIfError(this)
@@ -0,0 +1,15 @@
// TARGET_BACKEND: JVM_IR
object K : Code("K")
open class Code(val x: String) {
override fun toString() = "$x"
}
class O {
companion object: Code("O")
}
fun box(): String {
return "$O" + "$K" // must not be evaluated during compile time
}
@@ -0,0 +1,18 @@
// FIR_IDENTICAL
object O : Code(0)
open class Code(val x: Int) {
override fun toString() = "$x"
}
class A {
companion object: Code(0)
}
const val toString1 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>O.toString()<!>
const val toString2 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>A.toString()<!>
const val plusString1 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"string" + O<!>
const val plusString2 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"string" + A<!>
const val stringConcat1 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"$O"<!>
const val stringConcat2 = <!CONST_VAL_WITH_NON_CONST_INITIALIZER!>"$A"<!>
@@ -0,0 +1,40 @@
package
public const val plusString1: kotlin.String
public const val plusString2: kotlin.String
public const val stringConcat1: kotlin.String
public const val stringConcat2: kotlin.String
public const val toString1: kotlin.String
public const val toString2: kotlin.String
public final class A {
public constructor A()
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
public companion object Companion : Code {
private constructor Companion()
public final override /*1*/ /*fake_override*/ val x: kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
}
public open class Code {
public constructor Code(/*0*/ x: kotlin.Int)
public final val x: kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ fun toString(): kotlin.String
}
public object O : Code {
private constructor O()
public final override /*1*/ /*fake_override*/ val x: kotlin.Int
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -20501,6 +20501,12 @@ public class DiagnosticTestGenerated extends AbstractDiagnosticTest {
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt");
}
@Test
@TestMetadata("stringConcatenationWithObject.kt")
public void testStringConcatenationWithObject() throws Exception {
runTest("compiler/testData/diagnostics/tests/modifiers/const/stringConcatenationWithObject.kt");
}
@Test
@TestMetadata("types.kt")
public void testTypes() throws Exception {
@@ -17033,6 +17033,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/evaluate/simpleCallBinary.kt");
}
@Test
@TestMetadata("stringConcatenationWithObject.kt")
public void testStringConcatenationWithObject() throws Exception {
runTest("compiler/testData/codegen/box/evaluate/stringConcatenationWithObject.kt");
}
@Test
@TestMetadata("thisPlusString.kt")
public void testThisPlusString() throws Exception {