[JVM IR] Limit local variables in do-while conditions.

Locals introduced in the body of a do-while loop are not
necessarily live at the do-while condition. For example,
if there is a continue in the body before the declaration:

    do {
      if (shouldContinue(x))
        continue
      val y = 32  // not always defined in the condition
      doSomething(y)
    } while (x < 2)

For locals referenced in the condition such code is rejected
by the frontend because a local referenced in the condition
must be always defined when you get there.

However, locals that are not used in the condition were always
put in the local variable table. This leads to invalid locals
information which can trip of debuggers and other build tools
such as the D8 dexer.

This patch only puts in locals information for locals actually
referenced in the local variable table for the condition.

^Fixes KT-51754
This commit is contained in:
Mads Ager
2022-04-05 14:17:55 +02:00
committed by Alexander Udalov
parent 32fa2fc476
commit 1e444e0451
6 changed files with 98 additions and 3 deletions
@@ -49,6 +49,12 @@ public class FirLocalVariableTestGenerated extends AbstractFirLocalVariableTest
runTest("compiler/testData/debug/localVariables/destructuringInLambdas.kt");
}
@Test
@TestMetadata("doWhile.kt")
public void testDoWhile() throws Exception {
runTest("compiler/testData/debug/localVariables/doWhile.kt");
}
@Test
@TestMetadata("emptyFun.kt")
public void testEmptyFun() throws Exception {
@@ -40,12 +40,17 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrCompositeImpl
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueParameterSymbol
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.*
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.JAVA_STRING_TYPE
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.TypeSystemCommonBackendContext
@@ -125,6 +130,7 @@ class Gap(val start: Label, val end: Label)
class VariableInfo(val declaration: IrVariable, val index: Int, val type: Type, val startLabel: Label) {
val gaps = mutableListOf<Gap>()
var explicitEndLabel: Label? = null
}
class ExpressionCodegen(
@@ -424,7 +430,8 @@ class ExpressionCodegen(
mv.visitLocalVariable(it.declaration.name.asString(), it.type.descriptor, null, start, gap.start, it.index)
start = gap.end
}
mv.visitLocalVariable(it.declaration.name.asString(), it.type.descriptor, null, start, endLabel, it.index)
val end = it.explicitEndLabel ?: endLabel
mv.visitLocalVariable(it.declaration.name.asString(), it.type.descriptor, null, start, end, it.index)
}
}
@@ -1102,17 +1109,54 @@ class ExpressionCodegen(
// We have a regular 'do-while' loop. Proceed as usual.
mv.fakeAlwaysFalseIfeq(continueLabel)
mv.fakeAlwaysFalseIfeq(endLabel)
data.withBlock(loopInfo) {
loop.body?.accept(this, data)?.discard()
mv.visitLabel(continueLabel)
loop.condition.markLineNumber(true)
loop.condition.accept(this, data).coerceToBoolean().jumpIfTrue(entry)
endUnreferencedDoWhileLocals(data, loop, continueLabel)
}
mv.mark(endLabel)
addInlineMarker(mv, false)
return unitValue
}
// Locals introduced in the body of a do-while loop are not necessarily declared when the condition is
// reached. For example, there could be a continue from the body before the local is declared:
//
// do {
// if (shouldContinue(x)) {
// continue
// }
// var y = 32 // this variable is not necessarily declared on the do-while condition
// doSomething(y)
// } while (x < 2)
//
// This is all fine for variables used in the condition. If a variable that is not definitely
// assigned is used in the condition, the frontend rightly rejects the code. However, for variables
// that are *not* referenced in the condition, we have to be conservative and make sure that
// they do not end up in the local variable table. Otherwise, the debugger and other build tools
// such as D8 will see locals information that makes no sense.
private fun endUnreferencedDoWhileLocals(blockInfo: BlockInfo, loop: IrDoWhileLoop, continueLabel: Label) {
val referencedValues = hashSetOf<IrValueSymbol>()
loop.condition.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitGetValue(expression: IrGetValue) {
referencedValues.add(expression.symbol)
super.visitGetValue(expression)
}
})
blockInfo.variables.forEach {
if (it.declaration.symbol !in referencedValues) {
it.explicitEndLabel = continueLabel
}
}
}
private fun unwindBlockStack(
endLabel: Label,
data: BlockInfo,
@@ -13,7 +13,7 @@ fun box() {
val (_, `_`, d) = A()
for ((_, q) in arrayOfA) {
Unit
}
""
+33
View File
@@ -0,0 +1,33 @@
// IGNORE_BACKEND: JVM
// FILE: test.kt
fun shouldContinue(i: Int) = i < 1
fun box() {
var x = 0
do {
var z = 2
if (shouldContinue(x++)) {
continue
}
// Introduce a variable `y` which is not defined on all control-flow
// paths to the while condition. Therefore, it should not be in the
// local variable table at the condition.
var y = 12
} while (x < z)
}
// EXPECTATIONS
// test.kt:6 box:
// test.kt:8 box: x:int=0:int
// test.kt:9 box: x:int=0:int, z:int=2:int
// test.kt:3 shouldContinue: i:int=0:int
// test.kt:9 box: x:int=1:int, z:int=2:int
// test.kt:10 box: x:int=1:int, z:int=2:int
// test.kt:16 box: x:int=1:int, z:int=2:int
// test.kt:8 box: x:int=1:int
// test.kt:9 box: x:int=1:int, z:int=2:int
// test.kt:3 shouldContinue: i:int=1:int
// test.kt:9 box: x:int=2:int, z:int=2:int
// test.kt:15 box: x:int=2:int, z:int=2:int
// test.kt:16 box: x:int=2:int, z:int=2:int
// test.kt:17 box: x:int=2:int
@@ -49,6 +49,12 @@ public class IrLocalVariableTestGenerated extends AbstractIrLocalVariableTest {
runTest("compiler/testData/debug/localVariables/destructuringInLambdas.kt");
}
@Test
@TestMetadata("doWhile.kt")
public void testDoWhile() throws Exception {
runTest("compiler/testData/debug/localVariables/doWhile.kt");
}
@Test
@TestMetadata("emptyFun.kt")
public void testEmptyFun() throws Exception {
@@ -49,6 +49,12 @@ public class LocalVariableTestGenerated extends AbstractLocalVariableTest {
runTest("compiler/testData/debug/localVariables/destructuringInLambdas.kt");
}
@Test
@TestMetadata("doWhile.kt")
public void testDoWhile() throws Exception {
runTest("compiler/testData/debug/localVariables/doWhile.kt");
}
@Test
@TestMetadata("emptyFun.kt")
public void testEmptyFun() throws Exception {