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"); 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 @Test
@TestMetadata("types.kt") @TestMetadata("types.kt")
public void testTypes() throws Exception { public void testTypes() throws Exception {
@@ -20495,6 +20495,12 @@ public class FirOldFrontendDiagnosticsTestGenerated extends AbstractFirDiagnosti
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt"); 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 @Test
@TestMetadata("types.kt") @TestMetadata("types.kt")
public void testTypes() throws Exception { public void testTypes() throws Exception {
@@ -20495,6 +20495,12 @@ public class FirOldFrontendDiagnosticsWithLightTreeTestGenerated extends Abstrac
runTest("compiler/testData/diagnostics/tests/modifiers/const/stdlibConstFun.kt"); 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 @Test
@TestMetadata("types.kt") @TestMetadata("types.kt")
public void testTypes() throws Exception { public void testTypes() throws Exception {
@@ -66,8 +66,12 @@ internal fun checkConstantArguments(
return checkConstantArguments(expression.compareToCall, session) return checkConstantArguments(expression.compareToCall, session)
} }
expression is FirStringConcatenationCall || expression is FirEqualityOperatorCall -> { 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 } checkConstantArguments(exp, session).let { return it }
}
} }
expression is FirGetClassCall -> { expression is FirGetClassCall -> {
var coneType = (expression as? FirCall)?.argument?.typeRef?.coneType 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"); 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 @Test
@TestMetadata("thisPlusString.kt") @TestMetadata("thisPlusString.kt")
public void testThisPlusString() throws Exception { 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 canEvaluateEnumValue(enumEntry: IrGetEnumValue, context: IrCall? = null): Boolean
abstract fun canEvaluateReference(reference: IrCallableReference<*>, 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 if (function is IrSimpleFunction && function.correspondingPropertySymbol != null) return true
return (mustCheckBody || function.isLocal) && !function.isContract() && !function.isMarkedAsEvaluateIntrinsic() return (mustCheckBody || function.isLocal) && !function.isContract() && !function.isMarkedAsEvaluateIntrinsic()
} }
@@ -54,11 +54,14 @@ class IrCompileTimeChecker(
private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean { private fun visitConstructor(expression: IrFunctionAccessExpression): Boolean {
return when { return when {
!visitValueParameters(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false !visitValueParameters(expression, null) || !mode.canEvaluateFunction(expression.symbol.owner, contextExpression) -> false
mode.canEvaluateBody(expression.symbol.owner) -> expression.symbol.owner.body?.accept(this, null) != false else -> expression.symbol.owner.visitBodyIfNeeded()
else -> true
} }
} }
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 { override fun visitCall(expression: IrCall, data: Nothing?): Boolean {
val owner = expression.symbol.owner val owner = expression.symbol.owner
if (!mode.canEvaluateFunction(owner, expression)) return false if (!mode.canEvaluateFunction(owner, expression)) return false
@@ -67,7 +70,7 @@ class IrCompileTimeChecker(
val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true val dispatchReceiverComputable = expression.dispatchReceiver?.accept(this, null) ?: true
val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true val extensionReceiverComputable = expression.extensionReceiver?.accept(this, null) ?: true
if (!visitValueParameters(expression, null)) return@saveContext false 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 return@saveContext dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
} }
} }
@@ -126,7 +129,19 @@ class IrCompileTimeChecker(
} }
override fun visitStringConcatenation(expression: IrStringConcatenation, data: Nothing?): Boolean { 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 { override fun visitGetObjectValue(expression: IrGetObjectValue, data: Nothing?): Boolean {
@@ -218,7 +233,7 @@ class IrCompileTimeChecker(
if (!mode.canEvaluateFunction(owner, contextExpression)) return false 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 return dispatchReceiverComputable && extensionReceiverComputable && bodyComputable
} }
@@ -55,8 +55,10 @@ class IrConstTransformer(
val result = try { val result = try {
interpreter.interpret(this, irFile) interpreter.interpret(this, irFile)
} catch (e: Throwable) { } catch (e: Throwable) {
if (!suppressExceptions) throw AssertionError("Error occurred while optimizing an expression:\n${this.dump()}", e) if (suppressExceptions) {
return IrErrorExpressionImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, this.type, e.message.toString()).warningIfError(this) return this
}
throw AssertionError("Error occurred while optimizing an expression:\n${this.dump()}", e)
} }
return if (failAsError) result.reportIfError(this) else result.warningIfError(this) 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"); 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 @Test
@TestMetadata("types.kt") @TestMetadata("types.kt")
public void testTypes() throws Exception { public void testTypes() throws Exception {
@@ -17033,6 +17033,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/evaluate/simpleCallBinary.kt"); 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 @Test
@TestMetadata("thisPlusString.kt") @TestMetadata("thisPlusString.kt")
public void testThisPlusString() throws Exception { public void testThisPlusString() throws Exception {