Skip to content

Commit

Permalink
Make assign-client's exception handler dialog work from proof-client.…
Browse files Browse the repository at this point in the history
… Make with_thrown_errors also re-throw Rust panics as Java exceptions.
  • Loading branch information
aweinstock314 committed Jan 29, 2020
1 parent 33cfa30 commit 53f51dd
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,20 @@
import java.net.URISyntaxException;
import java.util.Optional;

public class AssignClient extends Application implements ArisExceptionHandler {
public class AssignClient extends Application {

private static final Logger logger = LogManager.getLogger(AssignClient.class);
private static boolean doUpdate = false;
private static AssignClient instance;

private Update update;
private ModuleSelect mainWindow;
private JavaFXDialogArisExceptionHandler exceptionHandler;

public AssignClient() {
instance = this;
LibAssign.getInstance().setArisExceptionHandler(this);
exceptionHandler = new JavaFXDialogArisExceptionHandler();
LibAssign.getInstance().setArisExceptionHandler(exceptionHandler);
try {
File jarPath = new File(AssignClient.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()).getParentFile();
update = new Update(Update.Stream.CLIENT, jarPath);
Expand Down Expand Up @@ -253,72 +255,4 @@ public void setDescription(String desc) {
});
}

@Override
public void uncaughtException(Thread t, Throwable e, boolean fatal) {
Platform.runLater(() -> {
try {
Alert alert = new Alert(Alert.AlertType.ERROR);

if (mainWindow != null) {
alert.initModality(Modality.APPLICATION_MODAL);
alert.initOwner(mainWindow.getStage().getScene().getWindow());
}

alert.getDialogPane().setPrefHeight(Region.USE_COMPUTED_SIZE);
alert.getDialogPane().setPrefWidth(Region.USE_COMPUTED_SIZE);

alert.getDialogPane().setPrefWidth(600);
alert.getDialogPane().setPrefHeight(500);

alert.setTitle("Critical Error");
if (fatal) {
alert.setHeaderText("He's dead, Jim!");
alert.setContentText("An error has occurred and Aris was unable to recover\n" +
"A bug report was generated and sent to the Aris developers");
} else {
alert.setHeaderText("99 little bugs in the code\n" +
"99 little bugs\n" +
"Take one down, patch it around\n" +
"137 little bugs in the code");
alert.setContentText("An error has occurred and Aris has attempted to recover\n" +
"A bug report was generated and sent to the Aris developers\n" +
"It is recommended to restart the program in case Aris was unable to fully recover");
}

StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println(LibAssign.NAME + " " + LibAssign.VERSION);
e.printStackTrace(pw);
String exceptionText = sw.toString();

Label label = new Label("Error details:");

TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(false);

textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);

GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);

GridPane expContent = new GridPane();
expContent.setMinHeight(300);
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);

alert.getDialogPane().setExpandableContent(expContent);
alert.getDialogPane().setExpanded(true);

alert.showAndWait();
} catch (Throwable e1) {
logger.fatal("An error has occurred while attempting to show the error dialog");
logger.catching(Level.FATAL, e1);
logger.fatal("The program will now exit");
System.exit(1);
}
});
}
}
31 changes: 29 additions & 2 deletions libaris/src/main/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ pub mod java_interop {
use jni::objects::{JClass, JString, JValue, JObject};
use jni::sys::{jobject, jstring, jarray};

use std::panic::{catch_unwind, UnwindSafe};

fn jobject_to_string(env: &JNIEnv, obj: JObject) -> jni::errors::Result<String> {
Ok(String::from(env.get_string(JString::from(obj))?))
}
Expand All @@ -45,8 +47,33 @@ pub mod java_interop {
Ok(())
}

pub fn with_thrown_errors<A, F: FnOnce(&JNIEnv) -> jni::errors::Result<A>>(env: &JNIEnv, f: F) -> A {
f(env).unwrap_or_else(|e| { let _ = env.throw(&*format!("{:?}", e)); unsafe { std::mem::zeroed() } })
pub fn with_thrown_errors<A, F: FnOnce(&JNIEnv) -> jni::errors::Result<A> + UnwindSafe>(env: &JNIEnv, f: F) -> A {
use std::panic::{take_hook, set_hook, PanicInfo, Location};
let old_hook = take_hook();
let (tx, rx) = std::sync::mpsc::channel::<String>();
let mtx = std::sync::Mutex::new(tx);
set_hook(Box::new(move |info: &PanicInfo| {
let mut msg = format!("Panic at {:?}", info.location());
if let Some(e) = info.payload().downcast_ref::<&str>() {
msg += &*format!(": {:?}", e);
}
if let Some(e) = info.payload().downcast_ref::<String>() {
msg += &*format!(": {:?}", e);
}
if let Ok(tx) = mtx.lock() {
let _ = tx.send(msg);
}
}));
let ret = catch_unwind(|| {
f(env).unwrap_or_else(|e| { let _ = env.throw_new("java/lang/RuntimeException", &*format!("{:?}", e)); unsafe { std::mem::zeroed() } }) // handle Result::Err
}).unwrap_or_else(|_| {
// handle panic
let msg = rx.recv().unwrap_or(format!("with_thrown_errors: recv failed"));
let _ = env.throw_new("java/lang/RuntimeException", &*msg);
unsafe { std::mem::zeroed() }
});
set_hook(old_hook);
ret
}
}
use java_interop::*;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package edu.rpi.aris.assign;

import java.io.PrintWriter;
import java.io.StringWriter;

import javafx.application.Platform;
import javafx.event.Event;
import javafx.scene.control.*;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import javafx.stage.Modality;
import javafx.stage.Stage;

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class JavaFXDialogArisExceptionHandler implements ArisExceptionHandler {
private static final Logger logger = LogManager.getLogger(JavaFXDialogArisExceptionHandler.class);

@Override
public void uncaughtException(Thread t, Throwable e, boolean fatal) {
Platform.runLater(() -> {
try {
Alert alert = new Alert(Alert.AlertType.ERROR);

//if (mainWindow != null) {
// alert.initModality(Modality.APPLICATION_MODAL);
// alert.initOwner(mainWindow.getStage().getScene().getWindow());
//}

alert.getDialogPane().setPrefHeight(Region.USE_COMPUTED_SIZE);
alert.getDialogPane().setPrefWidth(Region.USE_COMPUTED_SIZE);

alert.getDialogPane().setPrefWidth(600);
alert.getDialogPane().setPrefHeight(500);

String[] messages = new String[]{
"An error has occurred and Aris was unable to recover",
//"A bug report was generated and sent to the Aris developers",
"Please submit the content of the error to the Aris developers",
"It is recommended to restart the program in case Aris was unable to fully recover"
};
alert.setTitle("Critical Error");
if (fatal) {
alert.setHeaderText("He's dead, Jim!");
alert.setContentText(String.format("%s\n%s", messages[0], messages[1]));
} else {
alert.setHeaderText("99 little bugs in the code\n" +
"99 little bugs\n" +
"Take one down, patch it around\n" +
"137 little bugs in the code");
alert.setContentText(String.format("%s\n%s\n%s", messages[0], messages[1], messages[2]));
}

StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println(LibAssign.NAME + " " + LibAssign.VERSION);
e.printStackTrace(pw);
String exceptionText = sw.toString();

Label label = new Label("Error details:");

TextArea textArea = new TextArea(exceptionText);
textArea.setEditable(false);
textArea.setWrapText(false);

textArea.setMaxWidth(Double.MAX_VALUE);
textArea.setMaxHeight(Double.MAX_VALUE);

GridPane.setVgrow(textArea, Priority.ALWAYS);
GridPane.setHgrow(textArea, Priority.ALWAYS);

GridPane expContent = new GridPane();
expContent.setMinHeight(300);
expContent.setMaxWidth(Double.MAX_VALUE);
expContent.add(label, 0, 0);
expContent.add(textArea, 0, 1);

alert.getDialogPane().setExpandableContent(expContent);
alert.getDialogPane().setExpanded(true);

alert.showAndWait();
} catch (Throwable e1) {
logger.fatal("An error has occurred while attempting to show the error dialog");
logger.catching(Level.FATAL, e1);
logger.fatal("The program will now exit");
System.exit(1);
}
});
}
}
1 change: 1 addition & 0 deletions proof-client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ mainClassName = 'edu.rpi.aris.gui.Aris'

dependencies {
compile project(':libaris')
compile project(':libassign')
compile project(':service')

compile 'org.apache.commons:commons-collections4:4.1'
Expand Down
4 changes: 4 additions & 0 deletions proof-client/src/main/java/edu/rpi/aris/gui/Aris.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ private <T> T onFXThread(Supplier<T> runnable) throws Exception {
@Override
public void start(Stage stage) throws IOException {
instance = this;
LibAssign libAssign = LibAssign.getInstance();
JavaFXDialogArisExceptionHandler exceptionHandler = new JavaFXDialogArisExceptionHandler();
libAssign.setArisExceptionHandler(exceptionHandler);
Thread.setDefaultUncaughtExceptionHandler(libAssign);
/*mainWindow = */
showProofWindow(stage, null);
}
Expand Down

0 comments on commit 53f51dd

Please sign in to comment.