Skip to content

Commit

Permalink
Add Parser
Browse files Browse the repository at this point in the history
  • Loading branch information
alonalbert committed May 2, 2024
1 parent 7662738 commit 266d19e
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 107 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ kotlin {
implementation("net.java.dev.jna:jna:${extra["jna.version"] as String}")
implementation("androidx.collection:collection:${extra["collections.version"] as String}")
implementation("org.jetbrains.androidx.lifecycle:lifecycle-runtime:${extra["lifecycle.version"] as String}")
runtimeOnly("org.jetbrains.skiko:skiko-awt-runtime-linux-x64:${extra["skiko.version"] as String}")
}
}
}
Expand Down
44 changes: 33 additions & 11 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/DexTextArea.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,20 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea
import java.awt.Graphics
import java.awt.Graphics2D

private val JumpPattern = Regex("(\\s+)[0-9a-fA-F]{4}: .+([0-9a-fA-F]{4}) // ([+-])[0-9a-fA-F]{4}[\\n\\r]*")
private val AddressedPattern = Regex("(\\s+)([0-9a-fA-F]{4}): .+[\\n\\r]*")
private val JumpPattern = Regex(".{9}[0-9a-fA-F]{4}: .+([0-9a-fA-F]{4}) // ([+-])[0-9a-fA-F]{4}[\\n\\r]*")
private val AddressedPattern = Regex(".{9}([0-9a-fA-F]{4}): .+[\\n\\r]*")

internal fun updateTextArea(textArea: RSyntaxTextArea, text: String) {
val position = textArea.caretPosition
textArea.text = text
textArea.caretPosition = minOf(position, textArea.document.length)
}

class DexTextArea : RSyntaxTextArea() {
class DexTextArea(private val explorerState: ExplorerState) : RSyntaxTextArea() {
private var displayJump = false
private var jumpRange = 0 to 0
private var horizontalOffsets = 0 to 0
private var fullText = ""

init {
addCaretListener { event ->
Expand All @@ -49,9 +50,9 @@ class DexTextArea : RSyntaxTextArea() {
var line = document.getText(start, end - start)
var result = JumpPattern.matchEntire(line)
if (result != null) {
val srcHorizontalOffset = start + result.groupValues[1].length
val targetAddress = result.groupValues[2]
val direction = if (result.groupValues[3] == "+") 1 else -1
val srcHorizontalOffset = start + line.countPadding()
val targetAddress = result.groupValues[1]
val direction = if (result.groupValues[2] == "+") 1 else -1

var dstLine = srcLine + direction
while (dstLine in 0..<lineCount) {
Expand All @@ -62,8 +63,8 @@ class DexTextArea : RSyntaxTextArea() {
result = AddressedPattern.matchEntire(line)
if (result == null) {
break
} else if (result.groupValues[2] == targetAddress) {
val dstHorizontalOffset = start + result.groupValues[1].length
} else if (result.groupValues[1] == targetAddress) {
val dstHorizontalOffset = start + line.countPadding()

displayJump = true
jumpRange = srcLine to dstLine
Expand All @@ -82,6 +83,17 @@ class DexTextArea : RSyntaxTextArea() {
}
}

override fun setText(text: String) {
fullText = text
refreshText()
}

fun refreshText() {
val saveCaret = caretPosition
super.setText(fullText.takeIf { explorerState.showLineNumbers } ?: fullText.removeLineLumbers())
caretPosition = saveCaret
}

override fun paintComponent(g: Graphics?) {
super.paintComponent(g)

Expand All @@ -97,10 +109,20 @@ class DexTextArea : RSyntaxTextArea() {
val x2 = bounds2.x.toInt() - padding
val y2 = (bounds2.y + lineHeight / 2).toInt()

val x0 = modelToView2D(2).x.toInt()
val g2 = g as Graphics2D
g2.drawLine(x1, y1, x1 / 2, y1)
g2.drawLine(x1 / 2, y1, x2 / 2, y2)
g2.drawLine(x2 / 2, y2, x2, y2)
g2.drawLine(x1, y1, x0, y1)
g2.drawLine(x0, y1, x0, y2)
g2.drawLine(x0, y2, x2, y2)
}
}
}

private val LineNumber = Regex("^( +\\d+: )([0-9a-f]{4}: )", RegexOption.MULTILINE)

private fun String.removeLineLumbers() = LineNumber.replace(this) {
val (start, end) = it.groupValues.drop(1)
" ".repeat(start.length) + end
}

private fun String.countPadding() = indexOfFirst { it != ' ' }
89 changes: 4 additions & 85 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Disassembly.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package dev.romainguy.kotlin.explorer

import dev.romainguy.kotlin.explorer.dex.DexDumpParser
import kotlinx.coroutines.*
import java.nio.file.Files
import java.nio.file.Path
Expand Down Expand Up @@ -95,7 +96,7 @@ suspend fun disassemble(
}

launch(ui) {
onDex(filterDex(dexdump.output))
onDex(DexDumpParser(dexdump.output).parseDexDump())
onStatusUpdate("AOT compilation…")
}

Expand Down Expand Up @@ -248,13 +249,7 @@ private fun cleanupClasses(directory: Path) {
.forEach { path -> path.toFile().delete() }
}

private val BuiltInKotlinClass = Regex("^(kotlin|kotlinx|java|javax|org\\.(intellij|jetbrains))\\..+")

private val DexCodePattern = Regex("^[0-9a-fA-F]+:[^|]+\\|([0-9a-fA-F]+: .+)")
private val DexMethodStartPattern = Regex("^\\s+#[0-9]+\\s+:\\s+\\(in L[^;]+;\\)")
private val DexMethodNamePattern = Regex("^\\s+name\\s+:\\s+'(.+)'")
private val DexMethodTypePattern = Regex("^\\s+type\\s+:\\s+'(.+)'")
private val DexClassNamePattern = Regex("^\\s+Class descriptor\\s+:\\s+'L(.+);'")
internal val BuiltInKotlinClass = Regex("^(kotlin|kotlinx|java|javax|org\\.(intellij|jetbrains))\\..+")

private val OatClassNamePattern = Regex("^\\d+: L([^;]+); \\(offset=[0-9a-zA-Zx]+\\) \\(type_idx=\\d+\\).+")
private val OatMethodPattern = Regex("^\\s+\\d+:\\s+(.+)\\s+\\(dex_method_idx=\\d+\\)")
Expand Down Expand Up @@ -319,83 +314,7 @@ private fun filterOat(oat: String) = buildString {
}
}

private fun filterDex(dex: String) = buildString {
val indent = " "
val lines = dex.lineSequence().iterator()

var insideClass = false
var insideMethod = false
var firstMethod = false
var firstClass = true
var className = "<UNKNOWN>"

while (lines.hasNext()) {
var line = lines.next()

var match: MatchResult? = null

if (insideClass) {
if (insideMethod) {
match = DexCodePattern.matchEntire(line)
if (match != null && match.groupValues.isNotEmpty()) {
appendLine("$indent${match.groupValues[1]}")
}
}

if (match === null) {
match = DexMethodStartPattern.matchEntire(line)
if (match != null) {
if (!lines.hasNext()) return@buildString
line = lines.next()

match = DexMethodNamePattern.matchEntire(line)
if (match != null && match.groupValues.isNotEmpty()) {
val name = match.groupValues[1]
if (!firstMethod) appendLine()
firstMethod = false

if (!lines.hasNext()) return@buildString
line = lines.next()

match = DexMethodTypePattern.matchEntire(line)
if (match != null && match.groupValues.isNotEmpty()) {
val type = match.groupValues[1]
appendLine(" $name$type // $className.$name()")
insideMethod = true
}
}
}
}
}

if (match === null) {
if (line.trim().startsWith("Class #")) {
if (!lines.hasNext()) return@buildString
line = lines.next()

match = DexClassNamePattern.matchEntire(line)
if (match != null && match.groupValues.isNotEmpty()) {
className = match.groupValues[1].replace('/', '.')

val suppress = className.matches(BuiltInKotlinClass)
if (!suppress) {
if (!firstClass) appendLine()
appendLine("class $className")
}

if (!lines.consumeUntil("Direct methods")) break

insideMethod = false
firstMethod = true
firstClass = false
insideClass = !suppress
}
}
}
}
}

private fun Iterator<String>.consumeUntil(prefix: String): Boolean {
internal fun Iterator<String>.consumeUntil(prefix: String): Boolean {
while (hasNext()) {
val line = next()
if (line.trim().startsWith(prefix)) return true
Expand Down
29 changes: 25 additions & 4 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/KotlinExplorer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ private fun FrameWindowScope.KotlinExplorer(
) {
// TODO: Move all those remembers to an internal private state object
var sourceTextArea by remember { mutableStateOf<RSyntaxTextArea?>(null) }
var dexTextArea by remember { mutableStateOf<RSyntaxTextArea?>(null) }
var dexTextArea by remember { mutableStateOf<DexTextArea?>(null) }
var oatTextArea by remember { mutableStateOf<RSyntaxTextArea?>(null) }
var activeTextArea by remember { mutableStateOf<RSyntaxTextArea?>(null) }
var status by remember { mutableStateOf("Ready") }
Expand Down Expand Up @@ -121,7 +121,14 @@ private fun FrameWindowScope.KotlinExplorer(
MainMenu(
explorerState,
sourceTextArea,
{ dex -> updateTextArea(dexTextArea!!, dex) },
{ dex ->
if (dex != null) {
updateTextArea(dexTextArea!!, dex)
} else {
dexTextArea?.refreshText()
}

},
{ oat -> updateTextArea(oatTextArea!!, oat) },
{ statusUpdate -> status = statusUpdate },
{ findDialog.isVisible = true },
Expand Down Expand Up @@ -183,7 +190,7 @@ private fun FrameWindowScope.KotlinExplorer(
SwingPanel(
modifier = Modifier.fillMaxSize(),
factory = {
dexTextArea = DexTextArea().apply {
dexTextArea = DexTextArea(explorerState).apply {
configureSyntaxTextArea(SyntaxConstants.SYNTAX_STYLE_NONE)
addFocusListener(focusTracker)
}
Expand Down Expand Up @@ -247,7 +254,7 @@ private fun FrameWindowScope.KotlinExplorer(
private fun FrameWindowScope.MainMenu(
explorerState: ExplorerState,
sourceTextArea: RSyntaxTextArea?,
onDexUpdate: (String) -> Unit,
onDexUpdate: (String?) -> Unit,
onOatUpdate: (String) -> Unit,
onStatusUpdate: (String) -> Unit,
onFindClicked: () -> Unit,
Expand Down Expand Up @@ -296,6 +303,20 @@ private fun FrameWindowScope.MainMenu(
),
onCheckedChange = { explorerState.presentationMode = it }
)
CheckboxItem(
"Show Line Numbers Mode",
explorerState.showLineNumbers,
shortcut = KeyShortcut(
key = Key.L,
ctrl = !isMac,
shift = true,
meta = isMac
),
onCheckedChange = {
explorerState.showLineNumbers = it
onDexUpdate(null)
}
)
}
Menu("Compilation") {
CheckboxItem(
Expand Down
17 changes: 10 additions & 7 deletions src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/State.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.readLines

private const val OPTIMIZE = "OPTIMIZE"
private const val PRESENTATION = "PRESENTATION"
private const val Optimize = "OPTIMIZE"
private const val Presentation = "PRESENTATION"
private const val ShowLineNumbers = "SHOW_LINE_NUMBERS"

@Stable
class ExplorerState(
val settings: Settings = Settings()
) {
var toolPaths by mutableStateOf(createToolPaths(settings))
var optimize by mutableStateOf(settings.getValue(OPTIMIZE, "true").toBoolean())
var presentationMode by mutableStateOf(settings.getValue(PRESENTATION, "false").toBoolean())
var optimize by mutableStateOf(settings.getValue(Optimize, "true").toBoolean())
var presentationMode by mutableStateOf(settings.getValue(Presentation, "false").toBoolean())
var showLineNumbers by mutableStateOf(settings.getValue(ShowLineNumbers, "true").toBoolean())
var sourceCode: String = readSourceCode(toolPaths)

fun reloadToolPathsFromSettings() {
Expand Down Expand Up @@ -80,9 +82,10 @@ fun writeState(state: ExplorerState) {
state.toolPaths.sourceFile,
state.sourceCode
)
state.settings.entries[OPTIMIZE] = state.optimize.toString()
state.settings.entries[PRESENTATION] = state.presentationMode.toString()

state.settings.entries[Optimize] = state.optimize.toString()
state.settings.entries[Presentation] = state.presentationMode.toString()
state.settings.entries[ShowLineNumbers] = state.showLineNumbers.toString()

Files.writeString(
state.settings.file,
state.settings.entries
Expand Down
Loading

0 comments on commit 266d19e

Please sign in to comment.