Added show Korim Bitmap action to the debugger

This commit is contained in:
soywiz
2019-12-29 20:25:43 +01:00
parent ccbaa7ee8c
commit b1f4429eb1
5 changed files with 139 additions and 51 deletions

View File

@@ -1,59 +1,79 @@
package com.soywiz.korge.intellij.debug
import com.sun.jdi.*
import kotlin.ByteArray
import javassist.util.proxy.*
import org.gradle.internal.id.UniqueId.*
import java.io.*
import java.lang.reflect.Method
import java.util.*
fun ArrayReference.convertToLocalBytes(): kotlin.ByteArray {
val base64Class = this.virtualMachine().getRemoteClass(Base64::class.java) ?: error("Can't find Base64 class")
val encoder = base64Class.invoke("getEncoder", listOf()) as ObjectReference
val str = encoder.invoke("encodeToString", listOf(this)) as StringReference
fun ArrayReference.convertToLocalBytes(thread: ThreadReference? = null): kotlin.ByteArray {
val base64Class = this.virtualMachine().getRemoteClass(Base64::class.java, thread) ?: error("Can't find Base64 class")
val encoder = base64Class.invoke("getEncoder", listOf(), thread = thread) as ObjectReference
val str = encoder.invoke("encodeToString", listOf(this), thread = thread) as StringReference
return Base64.getDecoder().decode(str.value())
}
fun ObjectReference.debugToLocalInstanceViaSerialization(): Any? {
return this.debugSerialize().debugDeserialize()
fun ObjectReference.debugToLocalInstanceViaSerialization(thread: ThreadReference? = null): Any? {
return this.debugSerialize(thread).debugDeserialize()
}
fun ObjectReference.debugSerialize(): ByteArray {
inline fun <reified T> Type?.instanceOf(): Boolean = instanceOf(T::class.java)
fun Type?.instanceOf(clazz: Class<*>): Boolean = instanceOf(clazz.name)
fun Type?.instanceOf(name: String): Boolean {
val type = this ?: return false
if (type is ClassType) {
//println("TYPE: ${type.signature()} ${type.name()} :: ${Bitmap::class.java.name}")
if (type.name() == name) return true
return type.superclass().instanceOf(name)
}
return false
}
fun ObjectReference.debugSerialize(thread: ThreadReference? = null): ByteArray {
val vm = this.virtualMachine()
val baosClass = vm.getRemoteClass(ByteArrayOutputStream::class.java) ?: error("Cant't find ByteArrayOutputStream")
val baosClassConstructor = baosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 0 } ?: error("Can't find ByteArrayOutputStream constructor")
val baos = baosClass.newInstance(vm.anyThread(), baosClassConstructor, listOf(), ClassType.INVOKE_SINGLE_THREADED)
val baosClass = vm.getRemoteClass(ByteArrayOutputStream::class.java, thread) ?: error("Cant't find ByteArrayOutputStream")
val baosClassConstructor =
baosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 0 } ?: error("Can't find ByteArrayOutputStream constructor")
val baos = baosClass.newInstance(thread ?: vm.anyThread(), baosClassConstructor, listOf(), ClassType.INVOKE_SINGLE_THREADED)
val oosClass = vm.getRemoteClass(ObjectOutputStream::class.java) ?: error("Can't find ObjectOutputStream")
val oosClassConstructor = oosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 1 } ?: error("Can't find ObjectOutputStream constructor")
val oos = oosClass.newInstance(vm.anyThread(), oosClassConstructor, listOf(baos), ClassType.INVOKE_SINGLE_THREADED)
val oosClass = vm.getRemoteClass(ObjectOutputStream::class.java, thread) ?: error("Can't find ObjectOutputStream")
val oosClassConstructor =
oosClass.methods().firstOrNull { it.isConstructor && it.arguments().size == 1 } ?: error("Can't find ObjectOutputStream constructor")
val oos = oosClass.newInstance(thread ?: vm.anyThread(), oosClassConstructor, listOf(baos), ClassType.INVOKE_SINGLE_THREADED)
oos.invoke("writeObject", listOf(this))
oos.invoke("writeObject", listOf(this), thread = thread)
return (baos.invoke("toByteArray", listOf()) as ArrayReference).convertToLocalBytes()
return (baos.invoke("toByteArray", listOf(), thread = thread) as ArrayReference).convertToLocalBytes(thread)
}
fun ByteArray.debugDeserialize(): Any? = ObjectInputStream(this.inputStream()).readObject()
fun VirtualMachine.getRemoteClass(clazz: Class<*>): ClassType? {
fun VirtualMachine.getRemoteClass(clazz: Class<*>, thread: ThreadReference? = null): ClassType? {
val clazzType = classesByName("java.lang.Class").firstOrNull() ?: error("Can't find java.lang.Class")
val clazzClassType = (clazzType as? ClassType?) ?: error("Invalid java.lang.Class")
val realClazz = clazzClassType.invoke("forName", listOf(mirrorOf(clazz.name))) as ClassObjectReference
val realClazz = clazzClassType.invoke("forName", listOf(mirrorOf(clazz.name)), thread = thread) as ClassObjectReference
return realClazz.reflectedType() as ClassType?
}
fun VirtualMachine.anyThread() = allThreads().first()
fun ClassType.invoke(methodName: String, args: List<Value>, signature: String? = null, thread: ThreadReference? = null): Value {
val method = if (signature != null) this.methodsByName(methodName, signature).first() else this.methodsByName(methodName).first()
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
try {
val method = if (signature != null) this.methodsByName(methodName, signature).first() else this.methodsByName(methodName).first()
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
} catch (e: IncompatibleThreadStateException) {
throw e
}
}
fun ObjectReference.invoke(methodName: String, args: List<Value>, signature: String? = null, thread: ThreadReference? = null): Value {
val method = if (signature != null) this.referenceType().methodsByName(methodName, signature).first() else this.referenceType().methodsByName(methodName).first()
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
try {
val method =
if (signature != null) this.referenceType().methodsByName(methodName, signature).first() else this.referenceType().methodsByName(methodName).first()
return this.invokeMethod(thread ?: this.virtualMachine().anyThread(), method, args, ClassType.INVOKE_SINGLE_THREADED)
} catch (e: IncompatibleThreadStateException) {
throw e
}
}
fun ClassType.getField(fieldName: String): Value = this.getValue(this.fieldByName(fieldName))
@@ -65,6 +85,7 @@ fun Value.int(default: Int): Int = if (this is PrimitiveValue) this.intValue() e
fun Value.bool(): Boolean? = if (this is PrimitiveValue) this.booleanValue() else null
fun Value.bool(default: Boolean): Boolean = if (this is PrimitiveValue) this.booleanValue() else default
/*
inline fun <reified T> Value.asLocalType() = asLocalType(T::class.java)
fun <T> Value.asLocalType(clazz: Class<T>): T {
@@ -75,3 +96,4 @@ fun <T> Value.asLocalType(clazz: Class<T>): T {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
} as T
}
*/

View File

@@ -0,0 +1,17 @@
package com.soywiz.korge.intellij.debug
import com.soywiz.korim.bitmap.*
import com.soywiz.korim.color.*
import com.sun.jdi.*
fun ObjectReference.readKorimBitmap32(thread: ThreadReference? = null): Bitmap32 {
val value = this
if (!value.type().instanceOf<Bitmap>()) error("Not a korim bitmap")
val width = value.invoke("getWidth", listOf(), thread = thread).int(0)
val height = value.invoke("getHeight", listOf(), thread = thread).int(0)
val premultiplied = value.invoke("getPremultiplied", listOf(), thread = thread).bool(false)
val bmp32Mirror = value.invoke("toBMP32", listOf(), thread = thread) as ObjectReference
val dataInstance = (bmp32Mirror.invoke("getData", listOf(), thread = thread) as ObjectReference).debugToLocalInstanceViaSerialization(thread = thread) as IntArray
return Bitmap32(width, height, RgbaArray(dataInstance.copyOf()), premultiplied)
}

View File

@@ -13,30 +13,20 @@ import java.awt.*
import javax.swing.*
import kotlin.jvm.internal.*
class BitmapDebugNodeRenderer : com.intellij.debugger.ui.tree.render.NodeRendererImpl("BitmapDebugNodeRenderer") {
override fun isApplicable(type: Type?): Boolean {
if (type == null) return false
if (type is ClassType) {
//println("TYPE: ${type.signature()} ${type.name()} :: ${Bitmap::class.java.name}")
if (type.name() == Bitmap::class.java.name) return true
return isApplicable(type.superclass())
}
return false
}
override fun isEnabled(): Boolean {
return true
}
override fun getUniqueId(): String {
return "BitmapDebugNodeRenderer"
class KorimBitmapDebugNodeRenderer : com.intellij.debugger.ui.tree.render.NodeRendererImpl(NAME) {
companion object {
const val NAME = "KorimBitmapDebugNodeRenderer"
}
override fun isApplicable(type: Type?): Boolean = type.instanceOf<Bitmap>()
override fun isEnabled(): Boolean = true
override fun getUniqueId(): String = NAME
override fun calcLabel(descriptor: ValueDescriptor, evaluationContext: EvaluationContext, listener: DescriptorLabelListener): String {
val value = descriptor.value
val thread = evaluationContext.suspendContext.thread?.threadReference
if (value is ObjectReference) {
val width = value.invoke("getWidth", listOf()).int()
val height = value.invoke("getHeight", listOf()).int()
val width = value.invoke("getWidth", listOf(), thread = thread).int()
val height = value.invoke("getHeight", listOf(), thread = thread).int()
return "${width}x${height}"
}
return value.toString()
@@ -47,14 +37,10 @@ class BitmapDebugNodeRenderer : com.intellij.debugger.ui.tree.render.NodeRendere
override fun calcValueIcon(descriptor: ValueDescriptor, evaluationContext: EvaluationContext, listener: DescriptorLabelListener): Icon? {
try {
val value = descriptor.value
val thread = evaluationContext.suspendContext.thread?.threadReference
val vm = value.virtualMachine()
if (value is ObjectReference) {
val width = value.invoke("getWidth", listOf()).int(0)
val height = value.invoke("getHeight", listOf()).int(0)
val premultiplied = value.invoke("getPremultiplied", listOf()).bool(false)
val bmp32Mirror = value.invoke("toBMP32", listOf()) as ObjectReference
val dataInstance = (bmp32Mirror.invoke("getData", listOf()) as ObjectReference).debugToLocalInstanceViaSerialization() as IntArray
val bmp32 = Bitmap32(width, height, RgbaArray(dataInstance), premultiplied)
val bmp32 = value.readKorimBitmap32(thread)
return ImageIcon(bmp32.toAwt().getScaledInstance(16, 16, Image.SCALE_SMOOTH))
}
} catch (e: Throwable) {

View File

@@ -0,0 +1,56 @@
package com.soywiz.korge.intellij.debug.actions
import com.intellij.debugger.actions.*
import com.intellij.debugger.engine.*
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.project.*
import com.intellij.xdebugger.impl.ui.tree.actions.*
import com.intellij.xdebugger.impl.ui.tree.nodes.*
import com.soywiz.korge.intellij.debug.*
import com.soywiz.korim.awt.*
import com.soywiz.korim.bitmap.*
import com.sun.jdi.*
import java.awt.*
import javax.swing.*
class ShowKorimBitmapAction : XDebuggerTreeActionBase(), DumbAware {
override fun perform(node: XValueNodeImpl, nodeName: String, e: AnActionEvent?) {
val javaValue = (node.valueContainer as JavaValue)
val descriptor = javaValue.descriptor
val value = descriptor.value
if (value.type().instanceOf<Bitmap>()) {
val thread = javaValue.evaluationContext.suspendContext.thread?.threadReference
val value = descriptor.value as ObjectReference
val bmp32 = value.readKorimBitmap32(thread)
val frame = JFrame()
frame.add(JLabel(ImageIcon(bmp32.clone().toAwt())))
frame.pack()
val dim: Dimension = Toolkit.getDefaultToolkit().screenSize
frame.setLocation(dim.width / 2 - frame.size.width / 2, dim.height / 2 - frame.size.height / 2)
frame.isVisible = true
}
}
override fun update(e: AnActionEvent) {
val values = ViewAsGroup.getSelectedValues(e)
e.presentation.isEnabledAndVisible = when {
values.size >= 1 -> {
val javaValue = values[0]
val value = javaValue.descriptor.value
value.type().instanceOf<Bitmap>()
}
else -> {
false
}
}
}
/*
override fun actionPerformed(e: AnActionEvent) {
val session = e.dataContext.getData(XDebugSession.DATA_KEY) ?: error("Can't find debug session")
val debuggerTree = e.dataContext.getData(XDebuggerTree.XDEBUGGER_TREE_KEY) ?: error("Can't find debugger tree")
val selectionPath = debuggerTree.selectionPath
println("ACTION!")
}
*/
}

View File

@@ -103,7 +103,7 @@
<applicationService serviceImplementation="com.soywiz.korge.intellij.config.KorgeGlobalSettings" />
<debugger.nodeRenderer implementation="com.soywiz.korge.intellij.debug.BitmapDebugNodeRenderer" />
<debugger.nodeRenderer implementation="com.soywiz.korge.intellij.debug.KorimBitmapDebugNodeRenderer" />
</extensions>
@@ -133,6 +133,13 @@
<separator/>
</group>
<group id="JavaDebuggerActions">
<action id="Debugger.ShowKorimBitmap" class="com.soywiz.korge.intellij.debug.actions.ShowKorimBitmapAction" text="Show _Bitmap" icon="/com/soywiz/korge/intellij/icon/korge.png" use-shortcut-of="XDebugger.ShowBitmap">
<add-to-group group-id="XDebugger.ValueGroup" anchor="last"/>
</action>
</group>
</actions>