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);