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 com.sun.jdi.ClassType
|
||||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
import org.jetbrains.org.objectweb.asm.*
|
||||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
import kotlin.jvm.internal.Lambda
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
|
||||||
|
|
||||||
class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||||
private companion object {
|
private companion object {
|
||||||
@@ -37,29 +36,54 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
|||||||
private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
|
private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
|
||||||
|
|
||||||
// Copied from com.intellij.debugger.ui.impl.watch.CompilingEvaluator.changeSuperToMagicAccessor
|
// 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 classWriter = ClassWriter(0)
|
||||||
val classVisitor = object : ClassVisitor(Opcodes.API_VERSION, classWriter) {
|
val classVisitor = object : ClassVisitor(Opcodes.API_VERSION, classWriter) {
|
||||||
override fun visit(
|
private var superName: String? = null
|
||||||
version: Int,
|
|
||||||
access: Int,
|
override fun visit(version: Int, access: Int, name: String, sig: String?, superName: String?, interfaces: Array<String>?) {
|
||||||
name: String,
|
|
||||||
signature: String?,
|
|
||||||
superName: String?,
|
|
||||||
interfaces: Array<String>?
|
|
||||||
) {
|
|
||||||
var newSuperName = superName
|
var newSuperName = superName
|
||||||
if ("java/lang/Object" == newSuperName) {
|
if (useMagicAccessor && "java/lang/Object" == newSuperName) {
|
||||||
newSuperName = "sun/reflect/MagicAccessorImpl"
|
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)
|
ClassReader(bytes).accept(classVisitor, 0)
|
||||||
return classWriter.toByteArray()
|
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 {
|
fun useMagicAccessor(context: ExecutionContext): Boolean {
|
||||||
val rawVersion = context.vm.version()?.substringBefore('_') ?: return false
|
val rawVersion = context.vm.version()?.substringBefore('_') ?: return false
|
||||||
val javaVersion = JavaSdkVersion.fromVersionString(rawVersion) ?: return false
|
val javaVersion = JavaSdkVersion.fromVersionString(rawVersion) ?: return false
|
||||||
@@ -105,7 +129,7 @@ class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for ((className, _, bytes) in classesToLoad) {
|
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)
|
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");
|
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")
|
@TestMetadata("localClass.kt")
|
||||||
public void testLocalClass() throws Exception {
|
public void testLocalClass() throws Exception {
|
||||||
runTest("idea/jvm-debugger/jvm-debugger-test/testData/evaluation/singleBreakpoint/localClass.kt");
|
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