mirror of
https://github.com/processing/processing4.git
synced 2026-06-16 04:26:26 +02:00
Updated error reporting
This commit is contained in:
@@ -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<StackFrame>()
|
||||
val processingFrames = mutableListOf<StackFrame>()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user