Skip to content

Commit

Permalink
end of week
Browse files Browse the repository at this point in the history
  • Loading branch information
junkdog committed Dec 3, 2023
1 parent 2e6957a commit cbd7673
Show file tree
Hide file tree
Showing 5 changed files with 267 additions and 37 deletions.
4 changes: 4 additions & 0 deletions core/src/main/kotlin/sift/core/asm/ins/MethodPrinter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package sift.core.asm.ins

class MethodPrinter {
}
2 changes: 1 addition & 1 deletion core/src/main/kotlin/sift/core/graphviz/Dot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ private fun edgeColors() = listOf(
Gruvbox.yellow2,
).shuffled()

private val TextStyle.hexColor
internal val TextStyle.hexColor
get() = color!!.toSRGB().toHex()

@Suppress("EnumEntryName")
Expand Down
115 changes: 115 additions & 0 deletions core/src/test/kotlin/sift/core/asm/ins/GraphvizBytecode.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package sift.core.asm.ins

import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.LineNumberNode
import org.objectweb.asm.tree.analysis.BasicValue
import sift.core.element.AsmMethodNode
import sift.core.graphviz.*
import sift.core.terminal.Gruvbox
import sift.core.terminal.Gruvbox.fg
import sift.core.terminal.TextTransformer

internal fun renderMethod(
mn: AsmMethodNode,
segments: List<LinearBytecodeSegment>
): String {
val sequentialIds = TextTransformer.uuidSequence()
val edges = segments
.flatMap { segment -> segment.successors().map { segment to it } }
.map { (from, to) -> nodeOf(from, sequentialIds) to nodeOf(to, sequentialIds) }
.map { (from, to) -> "$from -> $to" }

return """
|digraph {
| // setup
| graph [rankdir=TB, truecolor=true, bgcolor="black", margin=0.2, nodesep=0.2, ranksep=0.2];
| node [
| shape=plain;
| fontname="Courier New";
| fontcolor="#ebdbb2";
| fontsize=11;
| ];
| edge [
| arrowhead=normal;
| arrowtail=dot;
| fontcolor="#ebdbb2";
| color="${fg.hexColor}";
| fontname="verdana";
| fontsize=11;
| ];
|
| // nodes
| ${nodes(segments, sequentialIds)}
|
| // edges
| ${edges.joinToString("\n ")}
|
|}
""".trimMargin()
}

private fun nodeOf(
ins: LinearBytecodeSegment,
idShortener: TextTransformer
) = "SEGMENT_${idShortener(ins.id)}"

private fun nodes(
entities: List<LinearBytecodeSegment>,
idShortener: TextTransformer
): String {

fun describe(e: LinearBytecodeSegment): String {
return """
|${nodeOf(e, idShortener)}[label=<<table font="Courier New Bold" border="1" color="${Gruvbox.dark3.hexColor}" cellborder="0" cellspacing="0">
| ${tableRowsOf(e)}
| </table>>];
""".trimMargin()
}
return entities
.filter { it.instructions.filter { it.ins !is LineNumberNode }.size > 1 }
.map(::describe).joinToString("\n ")
}

private fun tableRowsOf(block: LinearBytecodeSegment): String {
val prefix = " "
return block.instructions
.map(SiftFrame<BasicValue>::ins)
.map(AbstractInsnNode::asTableRow)
.joinToString("\n|$prefix")
}

internal fun AbstractInsnNode.asTableRow(): String {
val trimmed = renderInstruction(this).trim()

val type = trimmed.substringBefore(" ")

val args = (trimmed.substringAfter(" ", "")
.takeUnless(String::isEmpty) ?: "&nbsp;")
.replace("<", "&lt;")
.replace(">", "&gt;")

val style = labelStyle

return (if (this is LabelNode) {
listOf(
"""<td bgcolor="${style.hexColor}" align="left" colspan="2"><font color="black">$type</font></td>""",
"""<td align="left"><font color="${argStyle.hexColor}">$args</font></td>""",
)
} else if (style.inverse == true) {
listOf(
"""<td bgcolor="${style.hexColor}" width="30">&nbsp;</td>""",
"""<td bgcolor="${style.hexColor}" align="left"><font color="black">$type</font></td>""",
"""<td align="left"><font color="${argStyle.hexColor}">$args</font></td>""",
)
} else {
listOf(
"""<td width="30">&nbsp;</td>""",
"""<td align="left"><font color="${style.hexColor}">$type</font></td>""",
"""<td align="left"><font color="${argStyle.hexColor}">$args</font></td>""",
)
}).joinToString("", prefix = "<tr>", postfix = "</tr>")
}


//fun Li
152 changes: 129 additions & 23 deletions core/src/test/kotlin/sift/core/asm/ins/MethodPrinterTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,64 @@ package sift.core.asm.ins
import net.onedaybeard.collectionsby.firstBy
import org.junit.jupiter.api.Test
import org.objectweb.asm.tree.AbstractInsnNode
import org.objectweb.asm.tree.JumpInsnNode
import org.objectweb.asm.tree.LabelNode
import org.objectweb.asm.tree.MethodInsnNode
import org.objectweb.asm.tree.MethodNode
import org.objectweb.asm.tree.TryCatchBlockNode
import org.objectweb.asm.tree.analysis.*
import sift.core.asm.classNode
import sift.core.element.AsmClassNode
import sift.core.element.AsmMethodNode
import sift.core.entity.EntityService
import sift.core.graphviz.DiagramGenerator
import java.util.UUID


class MethodPrinterTest {
@Test
fun `sandbox`() {
val method = "method with if"
// val method = "method with if"
val method = "build"
// val method = "register\$core"

val cn = classNode(TestClass::class)
// val cn = classNode(TestClass::class)
// val cn = classNode(EntityService::class)
// val cn = classNode(DiagramGenerator::class)
val cn = classNode(DiagramGenerator::class)
cn.methods
// .firstBy(AsmMethodNode::name, "build")
.firstBy(AsmMethodNode::name, method)
.instructions
.asIterable()
.map(::render)
.map { ins -> renderTerminal(ins, renderInstruction(ins)) }
.joinToString("\n")
.let(::println)

// flowAnalyze(cn, cn.methods.firstBy(AsmMethodNode::name, method))
// dataflowAnalyze(cn, cn.methods.firstBy(AsmMethodNode::name, method))
// defintionUseCase(cn, cn.methods.firstBy(AsmMethodNode::name, method))

analyze(cn, cn.methods.firstBy(AsmMethodNode::name, method))
val analyzer = SiftAnalyzer<BasicValue>(BasicInterpreter())
analyze(analyzer, cn, cn.methods.firstBy(AsmMethodNode::name, method))
// analyze(cn, cn.methods.firstBy(AsmMethodNode::name, "build"))


val graphviz = renderMethod(cn.methods.firstBy(AsmMethodNode::name, method), analyzer.linearBytecodeSegments())
graphviz.let(::println)
}
}

private fun analyze(owner: AsmClassNode, mn: AsmMethodNode) {
private fun analyze(
analyzer: SiftAnalyzer<BasicValue>,
owner: AsmClassNode,
mn: AsmMethodNode
) {
analyzer.analyze(owner.name, mn)
analyzer.linearBytecodeSegments()
}

private fun analyzeTypeFlow(owner: AsmClassNode, mn: AsmMethodNode) {
// val a: Analyzer<BasicValue> = TypeFlowAnalyzer<BasicValue>(BasicInterpreter(), mn.instructions.toList())
val a: Analyzer<BasicValue> = TypeFlowAnalyzer<BasicValue>(BasicInterpreter(), mn.instructions.toList())
try {
a.analyze(owner.name, mn)
Expand All @@ -50,6 +73,7 @@ private fun analyze(owner: AsmClassNode, mn: AsmMethodNode) {
// }



println("hi")
} catch (e: AnalyzerException) {
e.printStackTrace()
Expand Down Expand Up @@ -90,17 +114,37 @@ class TypeFlowAnalyzer<V : Value>(



class SiftFrame<V : Value>: Frame<V> {
internal class SiftFrame<V : Value>: Frame<V> {
constructor(numLocals: Int, maxStack: Int) : super(numLocals, maxStack)
constructor(frame: Frame<V>) : super(frame)

val successors: MutableSet<Frame<V>> = mutableSetOf()
lateinit var ins: AbstractInsnNode
var index: Int = -1
val successors: MutableSet<SiftFrame<V>> = mutableSetOf()
val predecessors: MutableSet<SiftFrame<V>> = mutableSetOf()

var segment: LinearBytecodeSegment? = null

var isExceptionPath: Boolean = false

override fun toString(): String {
return if (this::ins.isInitialized) {
"$index ${ins::class.simpleName}"
} else "$index "
}
}

@Suppress("UNCHECKED_CAST")
class SiftAnalyzer<V: Value>(
interpreter: Interpreter<V>
internal class SiftAnalyzer<V: Value>(
interpreter: Interpreter<V>,
) : Analyzer<V>(interpreter) {

private var instructions: List<AbstractInsnNode> = listOf()

override fun init(owner: String, method: MethodNode) {
instructions = method.instructions.toList()
}

override fun newFrame(frame: Frame<out V>): Frame<V> {
return SiftFrame(frame) as Frame<V>
}
Expand All @@ -110,25 +154,87 @@ class SiftAnalyzer<V: Value>(
}

override fun newControlFlowEdge(insnIndex: Int, successorIndex: Int) {
insnIndex.frame.successors += successorIndex.frame
insnIndex.frame.apply {
successors += successorIndex.frame
successorIndex.frame.predecessors += this
ins = insnIndex.ins
index = insnIndex
}
}

private val Int.frame: SiftFrame<V>
get() = frames[this] as SiftFrame<V>
override fun newControlFlowExceptionEdge(insnIndex: Int, successorIndex: Int): Boolean {
insnIndex.frame.apply {
successors += successorIndex.frame
successorIndex.frame.predecessors += this
ins = insnIndex.ins
index = insnIndex
isExceptionPath = true
}
return true
}

fun linearBytecodeSegments(): List<LinearBytecodeSegment> {
val segments: MutableList<LinearBytecodeSegment> = mutableListOf()

val current: MutableList<SiftFrame<BasicValue>> = mutableListOf()
for (f in frames.filter { it.index != -1 }) {
when (f.ins) {
is LabelNode -> {
if ((f.predecessors.size > 1 || f.successors.size > 1) && current.isNotEmpty()) {
segments += LinearBytecodeSegment(current.toList())
.also { current.first().segment = it }
current.clear()
}
current += f
}
is JumpInsnNode -> {
current += f
segments += LinearBytecodeSegment(current.toList())
.also { current.first().segment = it }
current.clear()
}
else -> {
current += f
}
}
}

if (current.isNotEmpty()) {
segments += LinearBytecodeSegment(current.toList())
.also { current.first().segment = it }
current.clear()
}

return segments
}

private val frames: List<SiftFrame<BasicValue>>
get() = getFrames().filterNotNull() as List<SiftFrame<BasicValue>>

private val Int.frame: SiftFrame<BasicValue>
get() = getFrames()[this] as SiftFrame<BasicValue>

private val Int.ins: SiftFrame<V>
get() = ins
private val Int.ins: AbstractInsnNode
get() = instructions[this]
}

data class LinearBytecodeSegment(
val instructions: List<AbstractInsnNode>
internal data class LinearBytecodeSegment(
val instructions: List<SiftFrame<BasicValue>>
) {
val id = UUID.randomUUID()

fun successors(): List<LinearBytecodeSegment> {
TODO()
return instructions
.last()
.successors
.mapNotNull(SiftFrame<BasicValue>::segment)
}

fun predecessor(): List<LinearBytecodeSegment> {
TODO()
return instructions
.first()
.predecessors
.mapNotNull(SiftFrame<BasicValue>::segment)
}
}

Expand All @@ -138,10 +244,10 @@ class TestClass {
println(c)
}

fun `method with if`(a: Int, b: Int) {
val c = if (a > b) a else b
println(c)
}
fun `method with if`(a: Int, b: Int) {
val c = if (a > b) a else b
println(c)
}

fun `method with return statement`(a: Int, b: Int): Int {
val c = if (a > b) a else b
Expand Down
Loading

0 comments on commit cbd7673

Please sign in to comment.