diff --git a/jadx-core/src/main/java/jadx/core/Jadx.java b/jadx-core/src/main/java/jadx/core/Jadx.java index 0c42052fb..cdc8f5d39 100644 --- a/jadx-core/src/main/java/jadx/core/Jadx.java +++ b/jadx-core/src/main/java/jadx/core/Jadx.java @@ -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; } } diff --git a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java index 9896d94a6..548fe16e6 100644 --- a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java +++ b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java index 625fee433..2d83934d6 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxProject.java @@ -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); } } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ExceptionDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/ExceptionDialog.java new file mode 100644 index 000000000..23c7fadfc --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/ExceptionDialog.java @@ -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("

An error occurred

Jadx encountered an unexpected error.

"); + + Map 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 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("Create a new issue at GitHub", 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(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 21a6b38da..9032a8b93 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -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();