Skip to content
This repository has been archived by the owner on Apr 16, 2024. It is now read-only.

Commit

Permalink
feat: remember the status per code attribute instead of having one gl…
Browse files Browse the repository at this point in the history
…obal state (#70)
  • Loading branch information
Mouwrice authored Jul 28, 2023
1 parent 0cb4528 commit 0cba189
Show file tree
Hide file tree
Showing 13 changed files with 555 additions and 518 deletions.
1 change: 0 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ kotlin {
implementation(libs.google.gson)
implementation(libs.materialkolor)
implementation(libs.dansoftowner.jthemedetecor)
// implementation("com.guardsquare:proguard-core:9.0.9")
implementation("com.github.Guardsquare:proguard-core:PR104-SNAPSHOT")
implementation(libs.bonsai.core)
}
Expand Down
80 changes: 5 additions & 75 deletions src/jvmMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,107 +22,37 @@ import androidx.compose.ui.window.application
import com.darkrockstudios.libraries.mpfilepicker.FilePicker
import com.jthemedetecor.OsThemeDetector
import com.materialkolor.AnimatedDynamicMaterialTheme
import proguard.classfile.ClassPool
import proguard.classfile.attribute.Attribute.CODE
import proguard.classfile.attribute.visitor.AllAttributeVisitor
import proguard.classfile.attribute.visitor.AttributeNameFilter
import proguard.classfile.visitor.AllClassVisitor
import proguard.classfile.visitor.AllMethodVisitor
import proguard.classfile.visitor.ClassPoolFiller
import proguard.evaluation.PartialEvaluator
import proguard.evaluation.util.jsonPrinter.JsonPrinter
import proguard.io.ClassFilter
import proguard.io.ClassReader
import proguard.io.DataEntrySource
import proguard.io.FileSource
import proguard.io.JarReader
import ui.Controls
import ui.fileview.FileViewer
import ui.stateview.StateViewer
import viewmodel.DebuggerViewModel
import java.io.File
import viewmodel.FilesViewModel
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
import kotlin.io.path.inputStream

@Composable
fun App() {
val viewModel by rememberSaveable { mutableStateOf(DebuggerViewModel()) }
val viewModel = rememberSaveable { FilesViewModel() }
var showFilePicker by remember { mutableStateOf(false) }
var loadFile by remember { mutableStateOf<Path?>(null) }
var loadJsonString by remember { mutableStateOf<Pair<Path, String>?>(null) }

// State change done here to avoid invalid state issues in the lifecycles.
loadFile?.let {
viewModel.loadJson(it)
loadFile = null
}

loadJsonString?.let {
viewModel.loadJson(it.first, it.second)
loadJsonString = null
}

Box(Modifier.fillMaxSize().padding(all = 16.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Controls(viewModel) {
Controls(viewModel.currentCodeAttribute) {
showFilePicker = it
}

Row(horizontalArrangement = Arrangement.spacedBy(16.dp)) {
FileViewer(viewModel)
StateViewer(viewModel)
StateViewer(viewModel.currentCodeAttribute)
}
}

// Accept json files
FilePicker(showFilePicker, fileExtensions = listOf("json", "jar")) { path ->
showFilePicker = false
if (path != null) {
if (path.path.endsWith(".jar")) {
val classPool = ClassPool()

val source: DataEntrySource = FileSource(
File(path.path),
)

source.pumpDataEntries(
JarReader(
false,
ClassFilter(
ClassReader(
false,
false,
false,
false,
null,
ClassPoolFiller(classPool),
),
),
),
)

val tracker = JsonPrinter()
val pe = PartialEvaluator.Builder.create()
.setEvaluateAllCode(true).setStateTracker(tracker).build()
classPool.accept(
AllClassVisitor(
AllMethodVisitor(
AllAttributeVisitor(
AttributeNameFilter(CODE, pe),
),
),
// "proguard/evaluation/PartialEvaluator",
),
)

loadJsonString = Pair(Path.of(path.path), tracker.json)
} else {
// If we already have a view model, load the json file into it.
// Otherwise, create a new view model from the json file.
loadFile = Path.of(path.path)
}
viewModel.loadFile(Path.of(path.path))
}
}
}
Expand Down
22 changes: 14 additions & 8 deletions src/jvmMain/kotlin/ui/Controls.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,36 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import viewmodel.DebuggerViewModel
import viewmodel.CodeAttributeViewModel
import viewmodel.Display

@Composable
fun Controls(viewModel: DebuggerViewModel, setShowFilePicker: (Boolean) -> Unit) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.wrapContentSize(align = Alignment.Companion.CenterStart, unbounded = true)) {
fun Controls(viewModel: CodeAttributeViewModel?, setShowFilePicker: (Boolean) -> Unit) {
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.wrapContentSize(align = Alignment.Companion.CenterStart, unbounded = true),
) {
Button(onClick = { setShowFilePicker(true) }) {
Text("Open file")
}

OutlinedButton(enabled = viewModel.hasPrevious, onClick = { viewModel.previous() }) {
OutlinedButton(enabled = viewModel != null && viewModel.hasPrevious, onClick = { viewModel?.previous() }) {
Text("Previous")
}

OutlinedButton(enabled = viewModel.hasNext, onClick = { viewModel.next() }) {
OutlinedButton(enabled = viewModel != null && viewModel.hasNext, onClick = {
viewModel?.next()
}) {
Text("Next")
}

OutlinedButton(enabled = viewModel.codeAttribute != null, onClick = {
viewModel.switchDisplay()
OutlinedButton(enabled = viewModel != null, onClick = {
viewModel?.switchDisplay()
}) {
when (viewModel.display) {
when (viewModel?.display) {
Display.EVALUATIONS -> Text("Show results")
Display.RESULTS -> Text("Show evaluations")
else -> { Text("Show results") }
}
}
}
Expand Down
138 changes: 68 additions & 70 deletions src/jvmMain/kotlin/ui/codeview/CodeViewer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import ui.Colors
import viewmodel.DebuggerViewModel
import viewmodel.CodeAttributeViewModel
import viewmodel.Display
import java.lang.Integer.max

/**
* Display all methods and their instructions from the parsed code attributes.
*/
@Composable
fun CodeViewer(viewModel: DebuggerViewModel) {
fun CodeViewer(viewModel: CodeAttributeViewModel) {
val state = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()

Expand All @@ -53,8 +53,8 @@ fun CodeViewer(viewModel: DebuggerViewModel) {

coroutineScope.launch {
// Animate scroll to the first item
val index = viewModel.codeAttribute?.instructions?.withIndex()
?.find { it.value.offset == aimOffset[viewModel.display] }
val index = viewModel.codeAttribute.instructions.withIndex()
.find { it.value.offset == aimOffset[viewModel.display] }
if (index != null) {
val visibleItemCount = state.layoutInfo.visibleItemsInfo.size
state.animateScrollToItem(max(0, index.index - visibleItemCount / 2))
Expand All @@ -66,81 +66,79 @@ fun CodeViewer(viewModel: DebuggerViewModel) {
modifier = Modifier.fillMaxSize(),
) {
LazyColumn(state = state) {
viewModel.codeAttribute?.let { codeAttribute ->
// Get the length of the offset as string of the last instruction of the current code attribute
val maxOffsetLength = codeAttribute.instructions.last().offset.toString().length
// Get the length of the offset as string of the last instruction of the current code attribute
val maxOffsetLength = viewModel.codeAttribute.instructions.last().offset.toString().length

// Display the instructions of the current code attribute
codeAttribute.instructions.forEachIndexed { instructionIndex, instruction ->
val isCurrent = when (viewModel.display) {
Display.EVALUATIONS -> viewModel.evaluation?.instructionOffset == instruction.offset
Display.RESULTS -> viewModel.instructionIndex == instructionIndex
}
// Display the instructions of the current code attribute
viewModel.codeAttribute.instructions.forEachIndexed { instructionIndex, instruction ->
val isCurrent = when (viewModel.display) {
Display.EVALUATIONS -> viewModel.evaluation?.instructionOffset == instruction.offset
Display.RESULTS -> viewModel.instructionIndex == instructionIndex
}

var inCatch = false
// Display a try-catch block, if any
viewModel.evaluationBlock?.exceptionHandlerInfo?.let { exceptionHandler ->
// Display the start of a try-catch block
if (exceptionHandler.catchStartOffset == instruction.offset) {
item {
Text(
"Catch ${exceptionHandler.catchType}",
style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace,
modifier = Modifier
.fillMaxWidth()
.background(Colors.Red.value.copy(alpha = 0.2F))
.padding(horizontal = 16.dp, vertical = 6.dp),
)
}
var inCatch = false
// Display a try-catch block, if any
viewModel.evaluationBlock?.exceptionHandlerInfo?.let { exceptionHandler ->
// Display the start of a try-catch block
if (exceptionHandler.catchStartOffset == instruction.offset) {
item {
Text(
"Catch ${exceptionHandler.catchType}",
style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace,
modifier = Modifier
.fillMaxWidth()
.background(Colors.Red.value.copy(alpha = 0.2F))
.padding(horizontal = 16.dp, vertical = 6.dp),
)
}
// Display the end of a try-catch block
if (exceptionHandler.catchEndOffset == instruction.offset) {
item {
Text(
"End catch",
style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace,
modifier = Modifier
.fillMaxWidth()
.background(Colors.Red.value.copy(alpha = 0.2F))
.padding(horizontal = 16.dp, vertical = 6.dp),
)
}
}
// Display the end of a try-catch block
if (exceptionHandler.catchEndOffset == instruction.offset) {
item {
Text(
"End catch",
style = MaterialTheme.typography.bodySmall,
fontFamily = FontFamily.Monospace,
modifier = Modifier
.fillMaxWidth()
.background(Colors.Red.value.copy(alpha = 0.2F))
.padding(horizontal = 16.dp, vertical = 6.dp),
)
}

inCatch =
exceptionHandler.catchStartOffset <= instruction.offset && exceptionHandler.catchEndOffset > instruction.offset
}

// Display the current instruction
item(key = instruction.offset) {
// Highlight if the instruction is the current one
var color = MaterialTheme.colorScheme.surface
if (isCurrent) {
color = when (viewModel.display) {
Display.EVALUATIONS -> Colors.Red.value.copy(alpha = 0.5F)
Display.RESULTS -> Colors.LightGreen.value.copy(alpha = 0.5F)
}
} else if (viewModel.display == Display.RESULTS) {
// Highlight the instruction if it is a target of the current instruction
val currentInstruction = codeAttribute.instructions[viewModel.instructionIndex]
if (currentInstruction.finalTargetInstructions?.contains(instruction.offset) == true) {
color = Colors.Yellow.value.copy(alpha = 0.5F)
} else if (currentInstruction.finalOriginInstructions?.contains(instruction.offset) == true) {
color = Colors.Blue.value.copy(alpha = 0.5F)
}
}
inCatch =
exceptionHandler.catchStartOffset <= instruction.offset && exceptionHandler.catchEndOffset > instruction.offset
}

InstructionViewer(viewModel, instruction, maxOffsetLength, color, inCatch)
// Display the current instruction
item(key = instruction.offset) {
// Highlight if the instruction is the current one
var color = MaterialTheme.colorScheme.surface
if (isCurrent) {
color = when (viewModel.display) {
Display.EVALUATIONS -> Colors.Red.value.copy(alpha = 0.5F)
Display.RESULTS -> Colors.LightGreen.value.copy(alpha = 0.5F)
}
} else if (viewModel.display == Display.RESULTS) {
// Highlight the instruction if it is a target of the current instruction
val currentInstruction = viewModel.codeAttribute.instructions[viewModel.instructionIndex]
if (currentInstruction.finalTargetInstructions?.contains(instruction.offset) == true) {
color = Colors.Yellow.value.copy(alpha = 0.5F)
} else if (currentInstruction.finalOriginInstructions?.contains(instruction.offset) == true) {
color = Colors.Blue.value.copy(alpha = 0.5F)
}
}

// There is an error to display at the current instruction
codeAttribute.error?.let { error ->
if (isCurrent && error.instructionOffset == instruction.offset) {
item {
ErrorViewer(error)
}
InstructionViewer(viewModel, instruction, maxOffsetLength, color, inCatch)
}

// There is an error to display at the current instruction
viewModel.codeAttribute.error?.let { error ->
if (isCurrent && error.instructionOffset == instruction.offset) {
item {
ErrorViewer(error)
}
}
}
Expand Down
Loading

0 comments on commit 0cba189

Please sign in to comment.