From 4cca647724a265d01084580676d5eb7eeeace08f Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 1 Jul 2025 17:02:03 +0200 Subject: [PATCH] Updated error reporting --- app/src/processing/app/gradle/Exceptions.kt | 165 +++++++++--------- app/src/processing/app/gradle/GradleJob.kt | 55 +----- .../processing/app/gradle/GradleService.kt | 2 + .../src/main/kotlin/DependenciesTask.kt | 2 + 4 files changed, 90 insertions(+), 134 deletions(-) diff --git a/app/src/processing/app/gradle/Exceptions.kt b/app/src/processing/app/gradle/Exceptions.kt index 947d720a2..c24a6f4dd 100644 --- a/app/src/processing/app/gradle/Exceptions.kt +++ b/app/src/processing/app/gradle/Exceptions.kt @@ -1,103 +1,98 @@ package processing.app.gradle -import com.sun.jdi.ObjectReference +import com.sun.jdi.Location import com.sun.jdi.StackFrame -import com.sun.jdi.StringReference import com.sun.jdi.VirtualMachine import com.sun.jdi.event.ExceptionEvent import com.sun.jdi.request.EventRequest -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import processing.app.Messages +import processing.app.SketchException +import processing.app.ui.Editor // TODO: Consider adding a panel to the footer -class Exceptions { - companion object { - suspend fun listen(vm: VirtualMachine) { - try { - val manager = vm.eventRequestManager() +class Exceptions (val vm: VirtualMachine, val editor: Editor?) { + suspend fun listen() { + try { + val manager = vm.eventRequestManager() - val request = manager.createExceptionRequest(null, false, true) - request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD) - request.enable() + val request = manager.createExceptionRequest(null, false, true) + request.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD) + request.enable() - val queue = vm.eventQueue() - while (true) { - val eventSet = queue.remove() - for (event in eventSet) { - if (event is ExceptionEvent) { - printExceptionDetails(event) - event.thread().resume() - } + val queue = vm.eventQueue() + while (true) { + val eventSet = queue.remove() + for (event in eventSet) { + if (event is ExceptionEvent) { + printExceptionDetails(event) + event.thread().resume() } - eventSet.resume() - delay(10) } - } catch (e: Exception) { - Messages.log("Error while listening for exceptions: ${e.message}") + eventSet.resume() + delay(10) } - } - - fun printExceptionDetails(event: ExceptionEvent) { - val exception = event.exception() - val thread = event.thread() - val location = event.location() - val stackFrames = thread.frames() - - println("\n🚨 Exception Caught 🚨") - println("Type : ${exception.referenceType().name()}") - // TODO: Fix exception message retrieval -// println("Message : ${getExceptionMessage(exception)}") - println("Thread : ${thread.name()}") - println("Location : ${location.sourcePath()}:${location.lineNumber()}\n") - - // TODO: Map to .pde file again - // TODO: Communicate back to Editor - - // Separate stack frames - val userFrames = mutableListOf() - val processingFrames = mutableListOf() - - stackFrames.forEach { frame -> - val className = frame.location().declaringType().name() - if (className.startsWith("processing.")) { - processingFrames.add(frame) - } else { - userFrames.add(frame) - } - } - - // Print user frames first - println("šŸ” Stacktrace (Your Code First):") - userFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) } - - // Print Processing frames second - if (processingFrames.isNotEmpty()) { - println("\nšŸ”§ Processing Stacktrace (Hidden Initially):") - processingFrames.forEachIndexed { index, frame -> printStackFrame(index, frame) } - } - - println("──────────────────────────────────\n") - } - - fun printStackFrame(index: Int, frame: StackFrame) { - val location = frame.location() - val method = location.method() - println( - " #$index ${location.sourcePath()}:${location.lineNumber()} -> ${ - method.declaringType().name() - }.${method.name()}()" - ) - } - - // Extracts the exception's message - fun getExceptionMessage(exception: ObjectReference): String { - val messageMethod = exception.referenceType().methodsByName("getMessage").firstOrNull() ?: return "Unknown" - val messageValue = - exception.invokeMethod(null, messageMethod, emptyList(), ObjectReference.INVOKE_SINGLE_THREADED) - return (messageValue as? StringReference)?.value() ?: "Unknown" + } catch (e: Exception) { + Messages.log("Error while listening for exceptions: ${e.message}") } } + + fun printExceptionDetails(event: ExceptionEvent) { + val exception = event.exception() + val thread = event.thread() + val location = event.location().mapToPdeFile() + val stackFrames = thread.frames() + + val (processingFrames, userFrames) = stackFrames + .map{ + val location = it.location().mapToPdeFile() + val method = location.method() + it to "${method.declaringType().name()}.${method.name()}() @ ${location.sourcePath()}:${location.lineNumber()}" + } + .partition { + it.first.location().declaringType().name().startsWith("processing.") + } + + /* + We have 6 lines by default within the editor to display more information about the exception. + */ + + val message = """ + In Processing code: + #processingFrames + + In your code: + #userFrames + + """ + .trimIndent() + .replace("#processingFrames", processingFrames.joinToString("\n ") { it.second }) + .replace("#userFrames", userFrames.joinToString("\n ") { it.second }) + + val error = """ + Exception: ${exception.referenceType().name()} @ ${location.sourcePath()}:${location.lineNumber()} + """.trimIndent() + + println(message) + System.err.println(error) + + editor?.statusError(exception.referenceType().name()) + } + + fun Location.mapToPdeFile(): Location { + if(editor == null) return this + + // Check if the source is a .java file + val sketch = editor.sketch + sketch.code.forEach { code -> + if(code.extension != "java") return@forEach + if(sourceName() != code.fileName) return@forEach + return@mapToPdeFile this + } + + // TODO: Map to .pde file again, @see JavaBuild.placeException + // BLOCKED: Because we don't run the JavaBuild code.prepocOffset is empty + + return this + } } \ No newline at end of file diff --git a/app/src/processing/app/gradle/GradleJob.kt b/app/src/processing/app/gradle/GradleJob.kt index d67055a25..e16de0cf2 100644 --- a/app/src/processing/app/gradle/GradleJob.kt +++ b/app/src/processing/app/gradle/GradleJob.kt @@ -42,9 +42,6 @@ class GradleJob{ private val scope = CoroutineScope(Dispatchers.IO) private val cancel = GradleConnector.newCancellationTokenSource() - private val outputStream = PipedOutputStream() - private val errorStream = PipedOutputStream() - fun start() { val folder = service?.sketch?.folder ?: throw IllegalStateException("Sketch folder is not set") scope.launch { @@ -60,8 +57,10 @@ class GradleJob{ withCancellationToken(cancel.token()) addStateListener() addDebugging() - setStandardOutput(outputStream) - setStandardError(errorStream) + if(Base.DEBUG) { + setStandardOutput(System.out) + setStandardError(System.err) + } run() } }catch (e: Exception){ @@ -71,49 +70,6 @@ class GradleJob{ vm.value = null } } - // TODO: I'm sure this can be done better - scope.launch { - try { - InputStreamReader(PipedInputStream(outputStream)).buffered().use { reader -> - reader.lineSequence() - .forEach { line -> - if (cancel.token().isCancellationRequested) { - return@launch - } - if (state.value != State.RUNNING) { - return@forEach - } - service?.out?.println(line) - } - } - }catch (e: Exception){ - Messages.log("Error while reading output: ${e.message}") - } - } - scope.launch { - try { - InputStreamReader(PipedInputStream(errorStream)).buffered().use { reader -> - reader.lineSequence() - .forEach { line -> - if (cancel.token().isCancellationRequested) { - return@launch - } - if (state.value != State.RUNNING) { - return@forEach - } - when{ - line.contains("+[IMKClient subclass]: chose IMKClient_Modern") -> return@forEach - line.contains("+[IMKInputSession subclass]: chose IMKInputSession_Modern") -> return@forEach - line.startsWith("__MOVE__") -> return@forEach - else -> service?.err?.println(line) - } - } - } - }catch (e: Exception){ - Messages.log("Error while reading error: ${e.message}") - } - } - } fun cancel(){ @@ -169,7 +125,8 @@ class GradleJob{ scope.launch { val debugger = Debugger.connect(service?.debugPort) ?: return@launch vm.value = debugger - Exceptions.listen(debugger) + val exceptions = Exceptions(debugger, service?.editor) + exceptions.listen() } }) diff --git a/app/src/processing/app/gradle/GradleService.kt b/app/src/processing/app/gradle/GradleService.kt index af04f884b..4ef07f2d6 100644 --- a/app/src/processing/app/gradle/GradleService.kt +++ b/app/src/processing/app/gradle/GradleService.kt @@ -20,6 +20,7 @@ import kotlin.io.path.writeText // TODO: Test running examples // TODO: Report failures to the console // TODO: Highlight errors in the editor +// TODO: Stop running sketches if modern build system is turned off // TODO: ---- FUTURE ---- // TODO: Improve progress tracking @@ -114,6 +115,7 @@ class GradleService( "fullscreen" to false, // TODO: Implement "display" to 1, // TODO: Implement "external" to true, + "location" to null, // TODO: Implement "editor.location" to editor?.location?.let { "${it.x},${it.y}" }, //"awt.disable" to false, //"window.color" to "0xFF000000", // TODO: Implement diff --git a/java/gradle/src/main/kotlin/DependenciesTask.kt b/java/gradle/src/main/kotlin/DependenciesTask.kt index e847cc310..ced1cf5a9 100644 --- a/java/gradle/src/main/kotlin/DependenciesTask.kt +++ b/java/gradle/src/main/kotlin/DependenciesTask.kt @@ -52,6 +52,8 @@ abstract class DependenciesTask: DefaultTask() { } project.dependencies.add("implementation", project.files(dependencies) ) + // TODO: Mutating the dependencies of configuration ':implementation' after it has been resolved or consumed. This + // TODO: Add only if user is compiling for P2D or P3D // Add JOGL and Gluegen dependencies project.dependencies.add("runtimeOnly", "org.jogamp.jogl:jogl-all-main:2.5.0")