diff --git a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java
index 04d97653b..a1bdd649f 100644
--- a/jadx-gui/src/main/java/jadx/gui/JadxGUI.java
+++ b/jadx-gui/src/main/java/jadx/gui/JadxGUI.java
@@ -16,7 +16,6 @@ import jadx.gui.logs.LogCollector;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsData;
import jadx.gui.ui.MainWindow;
-import jadx.gui.ui.dialog.ExceptionDialog;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.NLS;
@@ -35,7 +34,6 @@ public class JadxGUI {
LogCollector.register();
printSystemInfo();
- ExceptionDialog.registerUncaughtExceptionHandler();
NLS.setLocale(settings.getLangLocale());
SwingUtilities.invokeLater(() -> {
LafManager.init(settings);
diff --git a/jadx-gui/src/main/java/jadx/gui/report/ExceptionData.java b/jadx-gui/src/main/java/jadx/gui/report/ExceptionData.java
new file mode 100644
index 000000000..9cbcd950f
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/report/ExceptionData.java
@@ -0,0 +1,19 @@
+package jadx.gui.report;
+
+final class ExceptionData {
+ private final Throwable exception;
+ private final String githubProject;
+
+ ExceptionData(Throwable exception, String githubProject) {
+ this.exception = exception;
+ this.githubProject = githubProject;
+ }
+
+ public Throwable getException() {
+ return exception;
+ }
+
+ public String getGithubProject() {
+ return githubProject;
+ }
+}
diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExceptionDialog.java b/jadx-gui/src/main/java/jadx/gui/report/ExceptionDialog.java
similarity index 87%
rename from jadx-gui/src/main/java/jadx/gui/ui/dialog/ExceptionDialog.java
rename to jadx-gui/src/main/java/jadx/gui/report/ExceptionDialog.java
index 276dda6f5..964e0abcd 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/ExceptionDialog.java
+++ b/jadx-gui/src/main/java/jadx/gui/report/ExceptionDialog.java
@@ -1,4 +1,4 @@
-package jadx.gui.ui.dialog;
+package jadx.gui.report;
import java.awt.BorderLayout;
import java.awt.Color;
@@ -8,7 +8,6 @@ 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;
@@ -37,8 +36,10 @@ import jadx.commons.app.JadxSystemInfo;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsData;
+import jadx.gui.ui.MainWindow;
import jadx.gui.utils.LafManager;
import jadx.gui.utils.Link;
+import jadx.gui.utils.TextStandardActions;
public class ExceptionDialog extends JDialog {
@@ -46,17 +47,8 @@ public class ExceptionDialog extends JDialog {
private static final String FMT_DETAIL_LENGTH = "-13";
- public static void registerUncaughtExceptionHandler() {
- Thread.setDefaultUncaughtExceptionHandler(ExceptionDialog::showExceptionDialog);
- }
-
- 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");
+ ExceptionDialog(MainWindow mainWindow, ExceptionData data) {
+ super(mainWindow, "Jadx Error");
this.getContentPane().setLayout(new BorderLayout());
JPanel titlePanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
@@ -84,6 +76,7 @@ public class ExceptionDialog extends JDialog {
LOG.error("failed to get program arguments", t);
}
+ Throwable ex = data.getException();
StringWriter stackTraceWriter = new StringWriter(1024);
ex.printStackTrace(new PrintWriter(stackTraceWriter));
final String stackTrace = stackTraceWriter.toString();
@@ -96,13 +89,13 @@ public class ExceptionDialog extends JDialog {
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";
+ String message = "Please describe what you did before the error occurred.\n\n";
+ message += "**IMPORTANT!** If the error occurs with a specific APK file please attach or provide link to apk file!\n\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 body = String.format("%s%s\n```\n%s\n```", message, detailsIssueBuilder, stackTrace);
String issueBody;
try {
@@ -112,13 +105,19 @@ public class ExceptionDialog extends JDialog {
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);
+
+ String project = data.getGithubProject();
+ if (!project.isEmpty()) {
+ String url = String.format("https://github.com/%s/issues/new?labels=bug&title=%s&body=%s",
+ project, issueTitle, issueBody);
+ Link issueLink = new Link("Create a new issue at GitHub", url);
+ c.gridy = 1;
+ titlePanel.add(issueLink, c);
+ }
JTextArea messageArea = new JTextArea();
+ TextStandardActions.attach(messageArea);
messageArea.setEditable(false);
messageArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
messageArea.setForeground(Color.BLACK);
@@ -173,7 +172,7 @@ public class ExceptionDialog extends JDialog {
try {
throwTestException();
} catch (Exception e) {
- showExceptionDialog(Thread.currentThread(), e);
+ new ExceptionDialog(null, new ExceptionData(e, JadxExceptionHandler.MAIN_PROJECT_STRING));
}
}
diff --git a/jadx-gui/src/main/java/jadx/gui/report/JadxExceptionHandler.java b/jadx-gui/src/main/java/jadx/gui/report/JadxExceptionHandler.java
new file mode 100644
index 000000000..b906eb0f2
--- /dev/null
+++ b/jadx-gui/src/main/java/jadx/gui/report/JadxExceptionHandler.java
@@ -0,0 +1,64 @@
+package jadx.gui.report;
+
+import org.jetbrains.annotations.Nullable;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import jadx.gui.ui.MainWindow;
+import jadx.plugins.tools.JadxPluginsTools;
+import jadx.plugins.tools.data.JadxPluginMetadata;
+
+import static jadx.plugins.tools.JadxExternalPluginsLoader.JADX_PLUGIN_CLASSLOADER_PREFIX;
+
+public class JadxExceptionHandler implements Thread.UncaughtExceptionHandler {
+ private static final Logger LOG = LoggerFactory.getLogger(JadxExceptionHandler.class);
+
+ public static final String MAIN_PROJECT_STRING = "skylot/jadx";
+
+ public static void register(MainWindow mainWindow) {
+ Thread.setDefaultUncaughtExceptionHandler(new JadxExceptionHandler(mainWindow));
+ }
+
+ private final MainWindow mainWindow;
+
+ private JadxExceptionHandler(MainWindow mainWindow) {
+ this.mainWindow = mainWindow;
+ }
+
+ @Override
+ public void uncaughtException(Thread thread, Throwable ex) {
+ LOG.error("Exception was thrown", ex);
+ new ExceptionDialog(mainWindow, buildExceptionData(ex));
+ }
+
+ private ExceptionData buildExceptionData(Throwable ex) {
+ for (StackTraceElement stackTraceElement : ex.getStackTrace()) {
+ String classLoaderName = stackTraceElement.getClassLoaderName();
+ if (classLoaderName != null && classLoaderName.startsWith(JADX_PLUGIN_CLASSLOADER_PREFIX)) {
+ // plugin exception
+ String jarName = classLoaderName.substring(JADX_PLUGIN_CLASSLOADER_PREFIX.length());
+ String pluginProject = resolvePluginByJarName(jarName);
+ LOG.debug("Report exception in plugin: {}", pluginProject);
+ return new ExceptionData(ex, pluginProject);
+ }
+ }
+ return new ExceptionData(ex, MAIN_PROJECT_STRING);
+ }
+
+ private String resolvePluginByJarName(String jarName) {
+ for (JadxPluginMetadata jadxPluginMetadata : JadxPluginsTools.getInstance().getInstalled()) {
+ if (jadxPluginMetadata.getJar().equals(jarName)) {
+ String githubProject = getGithubProject(jadxPluginMetadata.getLocationId());
+ return githubProject != null ? githubProject : "";
+ }
+ }
+ return "";
+ }
+
+ private static @Nullable String getGithubProject(String locationId) {
+ if (locationId.startsWith("github:")) {
+ return locationId.substring("github:".length()).replace(':', '/');
+ }
+ return null;
+ }
+}
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 df3dde984..639812445 100644
--- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
+++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
@@ -118,6 +118,8 @@ import jadx.gui.plugins.context.CommonGuiPluginsContext;
import jadx.gui.plugins.context.TreePopupMenuEntry;
import jadx.gui.plugins.mappings.RenameMappingsGui;
import jadx.gui.plugins.quark.QuarkDialog;
+import jadx.gui.report.ExceptionDialog;
+import jadx.gui.report.JadxExceptionHandler;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.data.SaveOptionEnum;
@@ -138,7 +140,6 @@ import jadx.gui.ui.codearea.theme.EditorThemeManager;
import jadx.gui.ui.dialog.ADBDialog;
import jadx.gui.ui.dialog.AboutDialog;
import jadx.gui.ui.dialog.CharsetDialog;
-import jadx.gui.ui.dialog.ExceptionDialog;
import jadx.gui.ui.dialog.GotoAddressDialog;
import jadx.gui.ui.dialog.LogViewerDialog;
import jadx.gui.ui.dialog.SearchDialog;
@@ -266,6 +267,7 @@ public class MainWindow extends JFrame {
this.editorThemeManager = new EditorThemeManager(settings);
JadxEventQueue.register();
+ JadxExceptionHandler.register(this);
resetCache();
initUI();
this.editorSyncManager = new EditorSyncManager(this, tabbedPane);
diff --git a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java
index d608d2599..f7d9b4e84 100644
--- a/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java
+++ b/jadx-plugins-tools/src/main/java/jadx/plugins/tools/JadxExternalPluginsLoader.java
@@ -23,6 +23,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException;
public class JadxExternalPluginsLoader implements JadxPluginLoader {
private static final Logger LOG = LoggerFactory.getLogger(JadxExternalPluginsLoader.class);
+ public static final String JADX_PLUGIN_CLASSLOADER_PREFIX = "jadx-plugin:";
+
private final List classLoaders = new ArrayList<>();
@Override
@@ -75,7 +77,7 @@ public class JadxExternalPluginsLoader implements JadxPluginLoader {
private void loadFromJar(Map, JadxPlugin> map, Path jar) {
try {
File jarFile = jar.toFile();
- String clsLoaderName = "jadx-plugin:" + jarFile.getName();
+ String clsLoaderName = JADX_PLUGIN_CLASSLOADER_PREFIX + jarFile.getName();
URL[] urls = new URL[] { jarFile.toURI().toURL() };
URLClassLoader pluginClsLoader = new URLClassLoader(clsLoaderName, urls, thisClassLoader());
classLoaders.add(pluginClsLoader);