feat(gui): dialog for showing exception details and creating an GitHub issue (PR #1399)

* chore(gui): Dialog for showing exception details and creating an GitHub issue
* directly throw test exception
* checkstyle
* minor
* log exception before the dialog is shown
This commit is contained in:
Jan S
2022-03-01 16:00:22 +01:00
committed by GitHub
parent 21dd17290b
commit 17fbc99f29
5 changed files with 206 additions and 3 deletions
+9 -1
View File
@@ -178,7 +178,14 @@ public class Jadx {
return passes;
}
public static final String VERSION_DEV = "dev";
private static String version;
public static String getVersion() {
if (version != null) {
return version;
}
try {
ClassLoader classLoader = Jadx.class.getClassLoader();
if (classLoader != null) {
@@ -188,6 +195,7 @@ public class Jadx {
Manifest manifest = new Manifest(is);
String ver = manifest.getMainAttributes().getValue("jadx-version");
if (ver != null) {
version = ver;
return ver;
}
}
@@ -196,6 +204,6 @@ public class Jadx {
} catch (Exception e) {
LOG.error("Can't get manifest file", e);
}
return "dev";
return VERSION_DEV;
}
}
+2 -1
View File
@@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
import jadx.cli.LogHelper;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.ui.ExceptionDialog;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.NLS;
@@ -29,7 +30,7 @@ public class JadxGUI {
printSystemInfo();
LafManager.init(settings);
NLS.setLocale(settings.getLangLocale());
ExceptionDialog.registerUncaughtExceptionHandler();
SwingUtilities.invokeLater(new MainWindow(settings)::init);
} catch (Exception e) {
LOG.error("Error: {}", e.getMessage(), e);
@@ -178,7 +178,7 @@ public class JadxProject {
buildGson(basePath).toJson(data, writer);
saved = true;
} catch (Exception e) {
LOG.error("Error saving project", e);
throw new RuntimeException("Error saving project", e);
}
}
}
@@ -0,0 +1,184 @@
package jadx.gui.ui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.management.ManagementFactory;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JadxDecompiler;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsAdapter;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.Link;
public class ExceptionDialog extends JDialog {
private static final Logger LOG = LoggerFactory.getLogger(ExceptionDialog.class);
private static final String FMT_DETAIL_LENGTH = "-13";
public static void registerUncaughtExceptionHandler() {
Thread.setDefaultUncaughtExceptionHandler((thread, ex) -> showExceptionDialog(thread, ex));
}
public static void showExceptionDialog(Thread thread, Throwable ex) {
LOG.error("Exception was thrown", ex);
new ExceptionDialog(thread, ex);
}
public ExceptionDialog(Thread thread, Throwable ex) {
super((Window) null, "Jadx Error");
this.getContentPane().setLayout(new BorderLayout());
JPanel titlePanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.CENTER;
c.gridx = 0;
c.weightx = 1.0;
c.insets = new Insets(2, 5, 5, 5);
JLabel titleLabel = new JLabel("<html><h1>An error occurred</h1><p>Jadx encountered an unexpected error.</p></html>");
Map<String, String> details = new LinkedHashMap<>();
details.put("Jadx version", JadxDecompiler.getVersion());
details.put("Java version", System.getProperty("java.version", "?"));
details.put("Java VM", String.format("%s %s", System.getProperty("java.vm.vendor", "?"),
System.getProperty("java.vm.name", "?")));
details.put("Platform", String.format("%s (%s %s)", System.getProperty("os.name", "?"),
System.getProperty("os.version", "?"), System.getProperty("os.arch", "?")));
Runtime runtime = Runtime.getRuntime();
details.put("Max heap size", String.format("%d MB", runtime.maxMemory() / (1024 * 1024)));
try {
// TODO: Use ProcessHandle.current().info().commandLine() once min Java is 9+
List<String> args = ManagementFactory.getRuntimeMXBean().getInputArguments();
details.put("Program args", args.stream().collect(Collectors.joining(" ")));
} catch (Throwable t) {
LOG.error("failed to get program arguments", t);
}
StringWriter stackTraceWriter = new StringWriter(1024);
ex.printStackTrace(new PrintWriter(stackTraceWriter));
final String stackTrace = stackTraceWriter.toString();
String issueTitle;
try {
issueTitle = URLEncoder.encode(ex.toString(), StandardCharsets.UTF_8.toString());
} catch (Exception e) {
LOG.error("URL encoding of title failed", e);
issueTitle = ex.getClass().getSimpleName();
}
String message = "Please describe what you did before the error occurred.\n";
message += "**IMPORTANT!** If the error occurs with a specific APK file please attach or provide link to apk file!\n";
StringBuilder detailsIssueBuilder = new StringBuilder();
details.forEach((key, value) -> detailsIssueBuilder.append(String.format("* %s: %s\n", key, value)));
String body = String.format("%s %s\n```\n%s\n```", message, detailsIssueBuilder, stackTrace);
String issueBody;
try {
issueBody = URLEncoder.encode(body, StandardCharsets.UTF_8.toString());
} catch (Exception e) {
LOG.error("URL encoding of body failed", e);
issueBody = "Please copy the displayed text in the Jadx error dialog and paste it here";
}
String url = String.format("https://github.com/skylot/jadx/issues/new?labels=bug&title=%s&body=%s", issueTitle, issueBody);
Link issueLink = new Link("<html><u><b>Create a new issue at GitHub</b></u></html>", url);
c.gridy = 0;
titlePanel.add(titleLabel, c);
c.gridy = 1;
titlePanel.add(issueLink, c);
JTextArea messageArea = new JTextArea();
messageArea.setEditable(false);
messageArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
messageArea.setForeground(Color.BLACK);
messageArea.setBackground(Color.WHITE);
StringBuilder detailsTextBuilder = new StringBuilder();
details.forEach((key, value) -> detailsTextBuilder.append(String.format("%" + FMT_DETAIL_LENGTH + "s: %s\n", key, value)));
messageArea.setText(detailsTextBuilder.toString() + "\n" + stackTrace);
JPanel buttonPanel = new JPanel();
JButton exitButton = new JButton("Terminate Jadx");
exitButton.addActionListener((event) -> System.exit(1));
buttonPanel.add(exitButton);
JButton closeButton = new JButton("Go back to Jadx");
closeButton.addActionListener((event) -> setVisible(false));
buttonPanel.add(closeButton);
JScrollPane messageAreaScroller = new JScrollPane(messageArea);
messageAreaScroller.setMinimumSize(new Dimension(600, 400));
messageAreaScroller.setPreferredSize(new Dimension(600, 400));
this.add(titlePanel, BorderLayout.NORTH);
this.add(messageAreaScroller, BorderLayout.CENTER);
this.add(buttonPanel, BorderLayout.SOUTH);
this.pack();
javax.swing.SwingUtilities.invokeLater(() -> messageAreaScroller.getVerticalScrollBar().setValue(0));
final Toolkit toolkit = Toolkit.getDefaultToolkit();
final Dimension screenSize = toolkit.getScreenSize();
final int x = (screenSize.width - getWidth()) / 2;
final int y = (screenSize.height - getHeight()) / 2;
setLocation(x, y);
getRootPane().registerKeyboardAction((event) -> setVisible(false),
KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
JComponent.WHEN_IN_FOCUSED_WINDOW);
this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
this.setVisible(true);
}
public static void throwTestException() {
try {
throw new RuntimeException("Inner exception message");
} catch (Exception e) {
throw new JadxRuntimeException("Outer exception message", e);
}
}
public static void showTestExceptionDialog() {
try {
throwTestException();
} catch (Exception e) {
showExceptionDialog(Thread.currentThread(), e);
}
}
public static void main(String[] args) {
JadxSettings settings = JadxSettingsAdapter.load();
LafManager.init(settings);
showTestExceptionDialog();
}
}
@@ -85,6 +85,7 @@ import ch.qos.logback.classic.Level;
import jadx.api.JadxArgs;
import jadx.api.JavaNode;
import jadx.api.ResourceFile;
import jadx.core.Jadx;
import jadx.core.utils.StringUtils;
import jadx.core.utils.Utils;
import jadx.core.utils.files.FileUtils;
@@ -824,6 +825,7 @@ public class MainWindow extends JFrame {
}
private void initMenuAndToolbar() {
final boolean devVersion = (Jadx.VERSION_DEV.equals(Jadx.getVersion()));
Action openAction = new AbstractAction(NLS.str("file.open_action"), ICON_OPEN) {
@Override
public void actionPerformed(ActionEvent e) {
@@ -1085,6 +1087,14 @@ public class MainWindow extends JFrame {
JMenu help = new JMenu(NLS.str("menu.help"));
help.setMnemonic(KeyEvent.VK_H);
help.add(logAction);
if (devVersion) {
help.add(new AbstractAction("Show sample error report") {
@Override
public void actionPerformed(ActionEvent e) {
ExceptionDialog.throwTestException();
}
});
}
help.add(aboutAction);
JMenuBar menuBar = new JMenuBar();