From 35aae2a7479d02dbf55bb1d83558ee5880dc72fa Mon Sep 17 00:00:00 2001 From: alonalbert Date: Fri, 3 May 2024 09:37:42 -0700 Subject: [PATCH] Support Jump Indicators on OAT View (#26) Notes: * DexTextArea & OatTextArea are trivial, but I think it results in a cleaner project. * The `oat` package currently contains only OatTextArea, but it would make sense to move the `oat` parsing there too. --- .../romainguy/kotlin/explorer/CodeTextArea.kt | 130 ++++++++++++++++++ .../romainguy/kotlin/explorer/DexTextArea.kt | 128 ----------------- .../kotlin/explorer/KotlinExplorer.kt | 14 +- .../dev/romainguy/kotlin/explorer/Regex.kt | 21 +++ .../kotlin/explorer/dex/DexDumpParser.kt | 5 +- .../kotlin/explorer/dex/DexTextArea.kt | 29 ++++ .../kotlin/explorer/oat/OatTextArea.kt | 27 ++++ 7 files changed, 219 insertions(+), 135 deletions(-) create mode 100644 src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/CodeTextArea.kt delete mode 100644 src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/DexTextArea.kt create mode 100644 src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/Regex.kt create mode 100644 src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexTextArea.kt create mode 100644 src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatTextArea.kt diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/CodeTextArea.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/CodeTextArea.kt new file mode 100644 index 00000000..20f771a8 --- /dev/null +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/CodeTextArea.kt @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2023 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.romainguy.kotlin.explorer + +import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea +import java.awt.Graphics +import java.awt.Graphics2D +import javax.swing.event.CaretEvent +import javax.swing.event.CaretListener + +open class CodeTextArea( + private val explorerState: ExplorerState, + jumpRegex: Regex, + addressedRegex: Regex, + private val lineNumberRegex: Regex?, +) : RSyntaxTextArea() { + private val jumpDetector = JumpDetector(jumpRegex, addressedRegex) + private var jumpOffsets: JumpOffsets? = null + private var fullText = "" + + + init { + addCaretListener(::caretUpdate) + } + + final override fun addCaretListener(listener: CaretListener?) { + super.addCaretListener(listener) + } + + 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) + jumpOffsets?.let { jump -> + val padding = 6 + + val bounds1 = modelToView2D(jump.src) + val bounds2 = modelToView2D(jump.dst) + + val x1 = bounds1.x.toInt() - padding + val y1 = (bounds1.y + lineHeight / 2).toInt() + + 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, x0, y1) + g2.drawLine(x0, y1, x0, y2) + g2.drawLine(x0, y2, x2, y2) + } + } + + private fun caretUpdate(event: CaretEvent) { + val oldJumpOffsets = jumpOffsets + jumpOffsets = null + val srcLine = getLineOfOffset(event.dot) + var start = getLineStartOffset(srcLine) + var end = getLineEndOffset(srcLine) + + val caretLine = document.getText(start, end - start).trimEnd() + val jump = jumpDetector.detectJump(caretLine) + if (jump != null) { + val srcOffset = start + caretLine.countPadding() + + var dstLine = srcLine + jump.direction + while (dstLine in 0.. - val oldDisplayJump = displayJump - val oldJumpRange = jumpRange - val oldHorizontalOffsets = horizontalOffsets - - displayJump = false - - val srcLine = getLineOfOffset(event.dot) - var start = getLineStartOffset(srcLine) - var end = getLineEndOffset(srcLine) - - var line = document.getText(start, end - start) - var result = JumpPattern.matchEntire(line) - if (result != null) { - 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..[0-9a-f]+) line=(?\\d+)$") @@ -103,10 +104,6 @@ internal class DexDumpParser(text: String) { private class DexInstruction(val address: String, val code: String) } -private fun MatchResult.getValue(group: String): String { - return groups[group]?.value ?: throw IllegalStateException("Value of $group not found in $value") -} - private fun String.getClassName() = getValue(ClassName) .removePrefix("L") diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexTextArea.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexTextArea.kt new file mode 100644 index 00000000..41dbcb6b --- /dev/null +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/dex/DexTextArea.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.romainguy.kotlin.explorer.dex + +import dev.romainguy.kotlin.explorer.CodeTextArea +import dev.romainguy.kotlin.explorer.ExplorerState + +private val DexJumpRegex = + Regex("^.{9}[0-9a-fA-F]{4}: .+(?
[0-9a-fA-F]{4}) // (?[+-])[0-9a-fA-F]{4}$") +private val DexAddressedRegex = Regex("^.{9}(?
[0-9a-fA-F]{4}): .+$") +private val DexLineNumberRegex = Regex("^(? +\\d+: )([0-9a-f]{4}: )") + +class DexTextArea(explorerState: ExplorerState) : + CodeTextArea(explorerState, DexJumpRegex, DexAddressedRegex, DexLineNumberRegex) + diff --git a/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatTextArea.kt b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatTextArea.kt new file mode 100644 index 00000000..796cc838 --- /dev/null +++ b/src/jvmMain/kotlin/dev/romainguy/kotlin/explorer/oat/OatTextArea.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023 Romain Guy + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dev.romainguy.kotlin.explorer.oat + +import dev.romainguy.kotlin.explorer.CodeTextArea +import dev.romainguy.kotlin.explorer.ExplorerState + +private val OatJumpRegex = + Regex("^ +0x[0-9a-fA-F]{8}: .+ (?[+-])\\d+ \\((?
0x[0-9a-fA-F]{8})\\)$") +private val OatAddressedRegex = Regex("^ +(?
0x[0-9a-fA-F]{8}): .+$") + +class OatTextArea(explorerState: ExplorerState) : + CodeTextArea(explorerState, OatJumpRegex, OatAddressedRegex, lineNumberRegex = null) \ No newline at end of file