Debugger: Provide fixed implementation of 'toString()' for lambdas (KT-32691)
This commit is contained in:
+40
-16
@@ -23,10 +23,9 @@ import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.ClassType
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
import kotlin.jvm.internal.Lambda
|
||||
|
||||
class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
private companion object {
|
||||
@@ -37,29 +36,54 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
|
||||
|
||||
// Copied from com.intellij.debugger.ui.impl.watch.CompilingEvaluator.changeSuperToMagicAccessor
|
||||
fun changeSuperToMagicAccessor(bytes: ByteArray): ByteArray {
|
||||
fun patchClass(bytes: ByteArray, useMagicAccessor: Boolean): ByteArray {
|
||||
val classWriter = ClassWriter(0)
|
||||
val classVisitor = object : ClassVisitor(Opcodes.API_VERSION, classWriter) {
|
||||
override fun visit(
|
||||
version: Int,
|
||||
access: Int,
|
||||
name: String,
|
||||
signature: String?,
|
||||
superName: String?,
|
||||
interfaces: Array<String>?
|
||||
) {
|
||||
private var superName: String? = null
|
||||
|
||||
override fun visit(version: Int, access: Int, name: String, sig: String?, superName: String?, interfaces: Array<String>?) {
|
||||
var newSuperName = superName
|
||||
if ("java/lang/Object" == newSuperName) {
|
||||
if (useMagicAccessor && "java/lang/Object" == newSuperName) {
|
||||
newSuperName = "sun/reflect/MagicAccessorImpl"
|
||||
}
|
||||
|
||||
super.visit(version, access, name, signature, newSuperName, interfaces)
|
||||
this.superName = newSuperName
|
||||
super.visit(version, access, name, sig, newSuperName, interfaces)
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
if (superName == "kotlin/jvm/internal/Lambda") {
|
||||
generateLambdaToString(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClassReader(bytes).accept(classVisitor, 0)
|
||||
return classWriter.toByteArray()
|
||||
}
|
||||
|
||||
private fun generateLambdaToString(classVisitor: ClassVisitor) {
|
||||
val stringBuilderType = Type.getType(StringBuilder::class.java)
|
||||
val lambdaType = Type.getType(Lambda::class.java)
|
||||
|
||||
val methodVisitor = classVisitor.visitMethod(Opcodes.ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null)
|
||||
InstructionAdapter(methodVisitor).apply {
|
||||
visitCode()
|
||||
anew(stringBuilderType)
|
||||
dup()
|
||||
invokespecial(stringBuilderType.internalName, "<init>", "()V", false)
|
||||
aconst(lambdaType.className + "#")
|
||||
invokevirtual(stringBuilderType.internalName, "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false)
|
||||
load(0, lambdaType)
|
||||
invokevirtual("java/lang/Object", "hashCode", "()I", false)
|
||||
invokevirtual(stringBuilderType.internalName, "append", "(I)Ljava/lang/StringBuilder;", false)
|
||||
invokevirtual(stringBuilderType.internalName, "toString", "()Ljava/lang/String;", false)
|
||||
areturn(Type.getType(String::class.java))
|
||||
visitMaxs(2, 1)
|
||||
visitEnd()
|
||||
}
|
||||
}
|
||||
|
||||
fun useMagicAccessor(context: ExecutionContext): Boolean {
|
||||
val rawVersion = context.vm.version()?.substringBefore('_') ?: return false
|
||||
val javaVersion = JavaSdkVersion.fromVersionString(rawVersion) ?: return false
|
||||
@@ -105,7 +129,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
}
|
||||
|
||||
for ((className, _, bytes) in classesToLoad) {
|
||||
val patchedBytes = if (useMagicAccessor(context)) changeSuperToMagicAccessor(bytes) else bytes
|
||||
val patchedBytes = patchClass(bytes, useMagicAccessor(context))
|
||||
defineClass(className, patchedBytes, context, classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -315,6 +315,11 @@ public class KotlinEvaluateExpressionTestGenerated extends AbstractKotlinEvaluat
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/kt7046localVarInInline.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("lambdaToString.kt")
|
||||
public void testLambdaToString() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/lambdaToString.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("localClass.kt")
|
||||
public void testLocalClass() throws Exception {
|
||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/localClass.kt");
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
fun main() {
|
||||
//Breakpoint!
|
||||
val a = 5
|
||||
}
|
||||
|
||||
private fun String.removeId(): String {
|
||||
return substringBeforeLast('#') + "#id"
|
||||
}
|
||||
|
||||
// EXPRESSION: {}.toString().removeId()
|
||||
// RESULT: "kotlin.jvm.internal.Lambda#id": Ljava/lang/String;
|
||||
|
||||
// EXPRESSION: { 1 + 2 }.toString().removeId()
|
||||
// RESULT: "kotlin.jvm.internal.Lambda#id": Ljava/lang/String;
|
||||
|
||||
// EXPRESSION: { s: String -> s.toString() }.toString().removeId()
|
||||
// RESULT: "kotlin.jvm.internal.Lambda#id": Ljava/lang/String;
|
||||
|
||||
// EXPRESSION: { l: List<String> -> l.first() }.toString().removeId()
|
||||
// RESULT: "kotlin.jvm.internal.Lambda#id": Ljava/lang/String;
|
||||
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
LineBreakpoint created at lambdaToString.kt:3
|
||||
Run Java
|
||||
Connected to the target VM
|
||||
lambdaToString.kt:3
|
||||
Compile bytecode for {}.toString().removeId()
|
||||
Compile bytecode for { 1 + 2 }.toString().removeId()
|
||||
Compile bytecode for { s: String -> s.toString() }.toString().removeId()
|
||||
Compile bytecode for { l: List<String> -> l.first() }.toString().removeId()
|
||||
Disconnected from the target VM
|
||||
|
||||
Process finished with exit code 0
|
||||
Reference in New Issue
Block a user