From 9dd5a9ef89663344b66a528820f1e3cbeabdbf6b Mon Sep 17 00:00:00 2001 From: S-trace Date: Wed, 11 Dec 2019 10:38:15 +0300 Subject: [PATCH 01/25] gui: More advanced implementation of renaming This implementation does not reload file after renaming, and so works faster. --- .../src/main/java/jadx/api/ICodeCache.java | 2 + .../src/main/java/jadx/api/JavaClass.java | 4 + .../java/jadx/api/impl/InMemoryCodeCache.java | 5 + .../java/jadx/api/impl/NoOpCodeCache.java | 4 + .../java/jadx/core/dex/nodes/ClassNode.java | 5 + .../main/java/jadx/gui/treemodel/JClass.java | 7 + .../src/main/java/jadx/gui/ui/MainWindow.java | 7 +- .../main/java/jadx/gui/ui/RenameDialog.java | 121 ++++++++++++++---- .../src/main/java/jadx/gui/ui/TabbedPane.java | 7 + .../gui/ui/codearea/AbstractCodeArea.java | 6 + .../ui/codearea/ClassCodeContentPanel.java | 4 + .../java/jadx/gui/ui/codearea/CodeArea.java | 5 + .../java/jadx/gui/ui/codearea/CodePanel.java | 10 ++ .../java/jadx/gui/ui/codearea/SmaliArea.java | 5 + .../main/java/jadx/gui/utils/JNodeCache.java | 6 + 15 files changed, 169 insertions(+), 29 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/ICodeCache.java b/jadx-core/src/main/java/jadx/api/ICodeCache.java index a078f79cf..bc121067b 100644 --- a/jadx-core/src/main/java/jadx/api/ICodeCache.java +++ b/jadx-core/src/main/java/jadx/api/ICodeCache.java @@ -8,4 +8,6 @@ public interface ICodeCache { @Nullable ICodeInfo get(String clsFullName); + + void remove(String clsFullName); } diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 9909c50b0..67d4aba53 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -57,6 +57,10 @@ public final class JavaClass implements JavaNode { cls.decompile(); } + public void refresh() { + cls.refresh(); + } + public synchronized String getSmali() { return cls.getSmali(); } diff --git a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java index 04d80fef8..8f36b4c0e 100644 --- a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java @@ -21,4 +21,9 @@ public class InMemoryCodeCache implements ICodeCache { public @Nullable ICodeInfo get(String clsFullName) { return storage.get(clsFullName); } + + @Override + public void remove(String clsFullName) { + storage.remove(clsFullName); + } } diff --git a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java index f764cde4e..cf738372c 100644 --- a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java @@ -16,4 +16,8 @@ public class NoOpCodeCache implements ICodeCache { public @Nullable ICodeInfo get(String clsFullName) { return null; } + + @Override + public void remove(String clsFullName) { + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index a73fb33b3..034f0dbec 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -289,6 +289,11 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { return codeInfo; } + public synchronized ICodeInfo refresh() { + load(); + return decompile(false); + } + @Override public void load() { for (MethodNode mth : getMethods()) { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index 46becf9ab..8f7aada3c 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -59,6 +59,13 @@ public class JClass extends JLoadableNode { update(); } + public synchronized void refresh() { + cls.refresh(); + loaded = true; + update(); + cls.unload(); + } + public synchronized void update() { removeAllChildren(); if (!loaded) { 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 503185d87..f28b6d7b6 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -396,6 +396,11 @@ public class MainWindow extends JFrame { cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount)); } + void resetIndex() { + int threadsCount = settings.getThreadsCount(); + cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount)); + } + private synchronized void runBackgroundJobs() { cancelBackgroundJobs(); backgroundWorker = new BackgroundWorker(cacheObject, progressPane); @@ -512,7 +517,7 @@ public class MainWindow extends JFrame { treeModel.reload(); } - private void reloadTree() { + void reloadTree() { treeReloading = true; treeModel.reload(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index b70f87704..f21b98a0e 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -10,6 +10,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.List; +import java.util.Map; import javax.swing.*; @@ -23,13 +24,18 @@ import jadx.api.JavaMethod; import jadx.api.JavaNode; import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.dex.visitors.RenameVisitor; import jadx.core.utils.files.InputFile; +import jadx.gui.treemodel.CodeNode; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JPackage; +import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.ui.codearea.CodeArea; +import jadx.gui.ui.codearea.CodePanel; +import jadx.gui.utils.CodeUsageInfo; import jadx.gui.utils.NLS; import jadx.gui.utils.TextStandardActions; @@ -105,42 +111,37 @@ public class RenameDialog extends JDialog { return String.format("%s %s = %s", type, id, renameText); } - private boolean updateDeobfMap(String renameText, RootNode root) { - Path deobfMapPath = getDeobfMapPath(root); + private boolean writeDeobfMapFile(Path deobfMapPath, List deobfMap) throws IOException { if (deobfMapPath == null) { - LOG.error("rename(): Failed deofbMapFile is null"); + LOG.error("updateDeobfMapFile(): deobfMapPath is null!"); return false; } - String alias = getNodeAlias(renameText); - LOG.info("rename(): {}", alias); - try { - List deobfMap = readAndUpdateDeobfMap(deobfMapPath, alias); - File tmpFile = File.createTempFile("deobf_tmp_", ".txt"); - try (FileOutputStream fileOut = new FileOutputStream(tmpFile)) { - for (String entry : deobfMap) { - fileOut.write(entry.getBytes()); - fileOut.write(System.lineSeparator().getBytes()); - } - } - File oldMap = File.createTempFile("deobf_bak_", ".txt"); - Files.copy(deobfMapPath, oldMap.toPath(), StandardCopyOption.REPLACE_EXISTING); - Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING); - Files.delete(oldMap.toPath()); - } catch (Exception e) { - LOG.error("rename(): Failed to write deofbMapFile {}", deobfMapPath, e); - return false; + + File tmpFile = File.createTempFile("deobf_tmp_", ".txt"); + FileOutputStream fileOut = new FileOutputStream(tmpFile); + for (String entry : deobfMap) { + fileOut.write(entry.getBytes()); + fileOut.write(System.lineSeparator().getBytes()); } + fileOut.close(); + File oldMap = File.createTempFile("deobf_bak_", ".txt"); + Files.copy(deobfMapPath, oldMap.toPath(), StandardCopyOption.REPLACE_EXISTING); + Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING); + Files.delete(oldMap.toPath()); return true; } - private List readAndUpdateDeobfMap(Path deobfMapPath, String alias) throws IOException { - List deobfMap = Files.readAllLines(deobfMapPath, StandardCharsets.UTF_8); + @NotNull + private List readDeobfMap(Path deobfMapPath) throws IOException { + return Files.readAllLines(deobfMapPath, StandardCharsets.UTF_8); + } + + private List updateDeobfMap(List deobfMap, String alias) { String id = alias.split("=")[0]; - LOG.info("Id = {}", id); int i = 0; while (i < deobfMap.size()) { if (deobfMap.get(i).startsWith(id)) { - LOG.info("Removing entry {}", deobfMap.get(i)); + LOG.info("updateDeobfMap(): Removing entry " + deobfMap.get(i)); deobfMap.remove(i); } else { i++; @@ -161,15 +162,79 @@ public class RenameDialog extends JDialog { dispose(); return; } - if (!updateDeobfMap(renameText, root)) { - LOG.error("rename(): updateDeobfMap() failed"); + if (!refreshDeobfMapFile(renameText, root)) { dispose(); return; } - mainWindow.reOpenFile(); + refreshState(root); dispose(); } + private boolean refreshDeobfMapFile(String renameText, RootNode root) { + List deobfMap; + Path deobfMapPath = getDeobfMapPath(root); + try { + deobfMap = readDeobfMap(deobfMapPath); + } catch (IOException e) { + LOG.error("rename(): readDeobfMap() failed"); + dispose(); + return false; + } + updateDeobfMap(deobfMap, getNodeAlias(renameText)); + try { + writeDeobfMapFile(deobfMapPath, deobfMap); + } catch (IOException e) { + LOG.error("rename(): writeDeobfMap() failed"); + dispose(); + return false; + } + try { + writeDeobfMapFile(deobfMapPath, deobfMap); + } catch (IOException e) { + LOG.error("rename(): updateDeobfMap() failed"); + dispose(); + return false; + } + return true; + } + + private void refreshState(RootNode rootNode) { + RenameVisitor renameVisitor = new RenameVisitor(); + renameVisitor.init(rootNode); + + mainWindow.getCacheObject().getNodeCache().refresh(node); + + TabbedPane tabbedPane = mainWindow.getTabbedPane(); + refreshTabs(tabbedPane); + refreshUsages(); + mainWindow.resetIndex(); + mainWindow.reloadTree(); + } + + private void refreshTabs(TabbedPane tabbedPane) { + for (Map.Entry panel : tabbedPane.getOpenTabs().entrySet()) { + ContentPanel contentPanel = panel.getValue(); + if (contentPanel instanceof ClassCodeContentPanel) { + JNode node = panel.getKey(); + node.getRootClass().refresh(); // Update code cache + ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel; + CodePanel javaPanel = codePanel.getJavaCodePanel(); + javaPanel.refresh(); + tabbedPane.refresh(node); + } + } + } + + private void refreshUsages() { + CodeUsageInfo usageInfo = mainWindow.getCacheObject().getUsageInfo(); + if (usageInfo == null) { + return; + } + for (CodeNode node : usageInfo.getUsageList(node)) { + node.getRootClass().refresh(); // Update code cache + } + } + private void initCommon() { KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); getRootPane().registerKeyboardAction(e -> dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java index ae0627b10..72ff21eb7 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java @@ -155,6 +155,13 @@ public class TabbedPane extends JTabbedPane { return panel; } + public void refresh(JNode node) { + ContentPanel panel = openTabs.get(node); + if (panel != null) { + setTabComponentAt(indexOfComponent(panel), makeTabComponent(panel)); + } + } + @Nullable private ContentPanel makeContentPanel(JNode node) { if (node instanceof JResource) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java index 90068eaa8..308f8f633 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeArea.java @@ -53,6 +53,12 @@ public abstract class AbstractCodeArea extends RSyntaxTextArea { */ public abstract void load(); + /** + * Implement in this method the code that reloads node from cache and sets the new content to be + * displayed + */ + public abstract void refresh(); + public static RSyntaxTextArea getDefaultArea(MainWindow mainWindow) { RSyntaxTextArea area = new RSyntaxTextArea(); area.setEditable(false); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java index ff9b1ebde..937795ef5 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java @@ -70,4 +70,8 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel { return javaCodePanel.getCodeArea(); } + public CodePanel getJavaCodePanel() { + return javaCodePanel; + } + } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java index 8e75db620..29364e256 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -51,6 +51,11 @@ public final class CodeArea extends AbstractCodeArea { } } + @Override + public void refresh() { + setText(node.getContent()); + } + private void addMenuItems() { FindUsageAction findUsage = new FindUsageAction(this); GoToDeclarationAction goToDeclaration = new GoToDeclarationAction(this); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java index f715092f3..45c97831a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodePanel.java @@ -87,4 +87,14 @@ public class CodePanel extends JPanel { public JScrollPane getCodeScrollPane() { return codeScrollPane; } + + public void refresh() { + JViewport viewport = getCodeScrollPane().getViewport(); + Point viewPosition = viewport.getViewPosition(); + codeArea.refresh(); + initLineNumbers(); + SwingUtilities.invokeLater(() -> { + viewport.setViewPosition(viewPosition); + }); + } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java index 41200a3bf..c2bacad60 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java @@ -25,6 +25,11 @@ public final class SmaliArea extends AbstractCodeArea { } } + @Override + public void refresh() { + load(); + } + @Override public JNode getNode() { // this area contains only smali without other node attributes diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java index 4d46e2b73..dee949204 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java @@ -29,6 +29,12 @@ public class JNodeCache { return jNode; } + public void refresh(JNode node) { + JavaNode javaNode = node.getJavaNode(); + cache.remove(javaNode); + makeFrom(javaNode); + } + private JNode convert(JavaNode node) { if (node == null) { return null; From 489fbb5e42243ca939eb529bcbd8fb6c714f33d6 Mon Sep 17 00:00:00 2001 From: S-trace Date: Wed, 11 Dec 2019 14:40:49 +0300 Subject: [PATCH 02/25] gui: Improve performance of renaming Fixes multiple decompilation of classes - now each class decompiled just once. --- jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index f21b98a0e..b36ee7767 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -9,6 +9,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -26,7 +27,6 @@ import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.RenameVisitor; import jadx.core.utils.files.InputFile; -import jadx.gui.treemodel.CodeNode; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; @@ -230,9 +230,10 @@ public class RenameDialog extends JDialog { if (usageInfo == null) { return; } - for (CodeNode node : usageInfo.getUsageList(node)) { - node.getRootClass().refresh(); // Update code cache - } + + HashSet usageClasses = new HashSet<>(); + usageInfo.getUsageList(node).forEach((node) -> usageClasses.add(node.getRootClass().getCls())); + usageClasses.forEach(JavaClass::refresh); } private void initCommon() { From 797904afb5463559d9a427e78cbb3d88dfa281db Mon Sep 17 00:00:00 2001 From: S-trace Date: Wed, 11 Dec 2019 18:23:02 +0300 Subject: [PATCH 03/25] gui: Perform refresh of non-displayed classes in background thread After renaming some classes needs to be redecompiled to reflect new state. Move recompilation of non-displayed classes to background thread. This should improve performance on weak machines. --- .../java/jadx/gui/jobs/BackgroundWorker.java | 9 ++- .../main/java/jadx/gui/jobs/RefreshJob.java | 28 +++++++ .../src/main/java/jadx/gui/ui/MainWindow.java | 12 ++- .../main/java/jadx/gui/ui/RenameDialog.java | 78 ++++++++++++++----- .../main/java/jadx/gui/utils/CacheObject.java | 10 +++ 5 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/jobs/RefreshJob.java diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java index b90da2d32..765e9a750 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java @@ -52,6 +52,13 @@ public class BackgroundWorker extends SwingWorker { System.gc(); LOG.debug("Memory usage: Before decompile: {}", UiUtils.memoryInfo()); runJob(cache.getDecompileJob()); + LOG.debug("Memory usage: After decompile: {}", UiUtils.memoryInfo()); + + LOG.info("Memory usage: Before refresh: {}", UiUtils.memoryInfo()); + long start = System.nanoTime(); + runJob(cache.getRefreshJob()); + cache.setRefreshJob(null); + LOG.info("Memory usage: After refresh: {}, refresh took {} ms", UiUtils.memoryInfo(), (System.nanoTime() - start) / 1000000); LOG.debug("Memory usage: Before index: {}", UiUtils.memoryInfo()); runJob(cache.getIndexJob()); @@ -74,7 +81,7 @@ public class BackgroundWorker extends SwingWorker { } private void runJob(BackgroundJob job) { - if (isCancelled()) { + if (isCancelled() || job == null) { return; } progressPane.changeLabel(this, job.getInfoString()); diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/RefreshJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/RefreshJob.java new file mode 100644 index 000000000..830feab45 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/jobs/RefreshJob.java @@ -0,0 +1,28 @@ +package jadx.gui.jobs; + +import java.util.Set; + +import jadx.api.JavaClass; +import jadx.gui.JadxWrapper; + +public class RefreshJob extends BackgroundJob { + + private Set refreshClasses; + + public RefreshJob(JadxWrapper jadxWrapper, int threadsCount, Set refreshClasses) { + super(jadxWrapper, threadsCount); + this.refreshClasses = refreshClasses; + } + + protected void runJob() { + for (final JavaClass cls : refreshClasses) { + addTask(cls::refresh); + } + } + + @Override + public String getInfoString() { + return "Refreshing: "; + } + +} 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 f28b6d7b6..6ed0bba4d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -396,12 +396,12 @@ public class MainWindow extends JFrame { cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount)); } - void resetIndex() { + public void resetIndex() { int threadsCount = settings.getThreadsCount(); cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount)); } - private synchronized void runBackgroundJobs() { + synchronized void runBackgroundJobs() { cancelBackgroundJobs(); backgroundWorker = new BackgroundWorker(cacheObject, progressPane); if (settings.isAutoStartJobs()) { @@ -414,6 +414,12 @@ public class MainWindow extends JFrame { } } + synchronized void runBackgroundRefreshAndIndexJobs() { + cancelBackgroundJobs(); + backgroundWorker = new BackgroundWorker(cacheObject, progressPane); + backgroundWorker.exec(); + } + public synchronized void cancelBackgroundJobs() { backgroundExecutor.cancelAll(); if (backgroundWorker != null) { @@ -517,7 +523,7 @@ public class MainWindow extends JFrame { treeModel.reload(); } - void reloadTree() { + public void reloadTree() { treeReloading = true; treeModel.reload(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index b36ee7767..9d84be936 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -12,6 +12,7 @@ import java.nio.file.StandardCopyOption; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.swing.*; @@ -27,6 +28,8 @@ import jadx.core.dex.nodes.DexNode; import jadx.core.dex.nodes.RootNode; import jadx.core.dex.visitors.RenameVisitor; import jadx.core.utils.files.InputFile; +import jadx.gui.jobs.IndexJob; +import jadx.gui.jobs.RefreshJob; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; @@ -35,6 +38,7 @@ import jadx.gui.treemodel.JPackage; import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.ui.codearea.CodeArea; import jadx.gui.ui.codearea.CodePanel; +import jadx.gui.utils.CacheObject; import jadx.gui.utils.CodeUsageInfo; import jadx.gui.utils.NLS; import jadx.gui.utils.TextStandardActions; @@ -152,6 +156,7 @@ public class RenameDialog extends JDialog { } private void rename() { + long start = System.nanoTime(); String renameText = renameField.getText(); if (renameText == null || renameText.length() == 0 || codeArea.getText() == null) { return; @@ -166,7 +171,13 @@ public class RenameDialog extends JDialog { dispose(); return; } - refreshState(root); + long refreshStart = System.nanoTime(); + int classes = refreshState(root); + long refreshTime = (System.nanoTime() - refreshStart) / 1000000; + long totalTime = (System.nanoTime() - start) / 1000000; + LOG.info("refreshState() took {} ms to update state, {} classes will be refreshed in background ({} ms total)", refreshTime, + classes, + totalTime); dispose(); } @@ -198,42 +209,73 @@ public class RenameDialog extends JDialog { return true; } - private void refreshState(RootNode rootNode) { + private int refreshState(RootNode rootNode) { RenameVisitor renameVisitor = new RenameVisitor(); renameVisitor.init(rootNode); mainWindow.getCacheObject().getNodeCache().refresh(node); - TabbedPane tabbedPane = mainWindow.getTabbedPane(); - refreshTabs(tabbedPane); - refreshUsages(); - mainWindow.resetIndex(); + Set updatedClasses = getUpdatedClasses(); + mainWindow.reloadTree(); + refreshTabs(mainWindow.getTabbedPane(), updatedClasses); + + if (updatedClasses.size() > 0) { + setRefreshTask(updatedClasses); + } + + return updatedClasses.size(); } - private void refreshTabs(TabbedPane tabbedPane) { + private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) { for (Map.Entry panel : tabbedPane.getOpenTabs().entrySet()) { ContentPanel contentPanel = panel.getValue(); if (contentPanel instanceof ClassCodeContentPanel) { JNode node = panel.getKey(); - node.getRootClass().refresh(); // Update code cache - ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel; - CodePanel javaPanel = codePanel.getJavaCodePanel(); - javaPanel.refresh(); - tabbedPane.refresh(node); + JClass rootClass = node.getRootClass(); + JavaClass javaClass = rootClass.getCls(); + if (updatedClasses.contains(javaClass) || node.getRootClass().getCls() == javaClass) { + LOG.info("Refreshing rootClass " + javaClass.getRawName()); + rootClass.refresh(); // Update code cache + ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel; + CodePanel javaPanel = codePanel.getJavaCodePanel(); + javaPanel.refresh(); + tabbedPane.refresh(node); + updatedClasses.remove(javaClass); + } } } } - private void refreshUsages() { + private Set getUpdatedClasses() { + Set usageClasses = new HashSet<>(); CodeUsageInfo usageInfo = mainWindow.getCacheObject().getUsageInfo(); - if (usageInfo == null) { - return; + if (usageInfo != null) { + usageInfo.getUsageList(node).forEach((node) -> { + JavaClass rootClass = node.getRootClass().getCls(); + // LOG.info("updateUsages(): Going to update class {}", rootClass.getRealFullName()); + usageClasses.add(rootClass); + }); + // usageClasses.parallelStream().forEach(JavaClass::refresh); } + return usageClasses; + } - HashSet usageClasses = new HashSet<>(); - usageInfo.getUsageList(node).forEach((node) -> usageClasses.add(node.getRootClass().getCls())); - usageClasses.forEach(JavaClass::refresh); + private void setRefreshTask(Set refreshClasses) { + CacheObject cache = mainWindow.getCacheObject(); + RefreshJob refreshJob = new RefreshJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses); + LOG.info("Waiting for old refreshJob"); + while (cache.getRefreshJob() != null) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + return; + } + } + LOG.info("Old refreshJob finished"); + cache.setRefreshJob(refreshJob); + cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), mainWindow.getCacheObject(), mainWindow.getSettings().getThreadsCount())); + mainWindow.runBackgroundRefreshAndIndexJobs(); } private void initCommon() { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java index fc47dd81c..4e523568c 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable; import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.IndexJob; +import jadx.gui.jobs.RefreshJob; import jadx.gui.ui.SearchDialog; import jadx.gui.utils.search.TextSearchIndex; @@ -14,6 +15,7 @@ public class CacheObject { private DecompileJob decompileJob; private IndexJob indexJob; + private RefreshJob refreshJob; private TextSearchIndex textIndex; private CodeUsageInfo usageInfo; @@ -89,4 +91,12 @@ public class CacheObject { public Set getLastSearchOptions() { return lastSearchOptions; } + + public RefreshJob getRefreshJob() { + return refreshJob; + } + + public void setRefreshJob(RefreshJob refreshJob) { + this.refreshJob = refreshJob; + } } From f90fc1d5ec8413cb9a4bcca03930e6c721fe4161 Mon Sep 17 00:00:00 2001 From: S-trace Date: Wed, 25 Dec 2019 20:24:58 +0300 Subject: [PATCH 04/25] core: ClassNode: Load recursively missing information on refresh() Fixes loss of static identifiers, comments, annotations and source file name after rename. --- .../java/jadx/core/dex/attributes/AType.java | 4 +++ .../java/jadx/core/dex/nodes/ClassNode.java | 30 ++++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 2cdab5b73..a18d953ab 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -86,5 +86,9 @@ public class AType { public static final Set> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList( FIELD_REPLACE, METHOD_INLINE, + COMMENTS, + RENAME_REASON, + JADX_WARN, + JADX_ERROR, SKIP_MTH_ARGS)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 034f0dbec..5615e858a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -46,6 +46,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class); private final DexNode dex; + private final ClassDef cls; private final int clsDefOffset; private final ClassInfo clsInfo; private AccessInfo accessFlags; @@ -70,6 +71,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { public ClassNode(DexNode dex, ClassDef cls) { this.dex = dex; + this.cls = cls; this.clsDefOffset = cls.getOffset(); this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex()); try { @@ -151,6 +153,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { this.fields = new ArrayList<>(); this.accessFlags = new AccessInfo(accessFlags, AFType.CLASS); this.parentClass = this; + this.cls = null; dex.addClassNode(this); } @@ -290,10 +293,35 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { } public synchronized ICodeInfo refresh() { - load(); + reloadRecursive(); return decompile(false); } + private void reloadRecursive() { + load(); + int sfIdx = cls.getSourceFileIndex(); + if (sfIdx != DexNode.NO_INDEX) { + String fileName = dex.getString(sfIdx); + addSourceFilenameAttr(fileName); + } + for (ClassNode innerCls : getInnerClasses()) { + innerCls.reloadRecursive(); + } + loadStaticInfo(); + loadAnnotations(cls); + } + + private void loadStaticInfo() { + try { + if (cls != null) { + loadStaticValues(cls, fields); + } + } catch (DecodeException e) { + LOG.error("Got DecodeException in loadStaticValues() for class {}", getRawName()); + e.printStackTrace(); + } + } + @Override public void load() { for (MethodNode mth : getMethods()) { From df520a11341e0fa3a08ad339d44ac07baade46b5 Mon Sep 17 00:00:00 2001 From: S-trace Date: Wed, 25 Dec 2019 19:57:02 +0300 Subject: [PATCH 05/25] core: ClassNode: Check is field really static or not in loadStaticValues() Fixes appearing of the 0/null/false initializers for instance final fields. --- jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 5615e858a..9e92e69a0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -171,7 +171,9 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { for (FieldNode f : staticFields) { - if (f.getAccessFlags().isFinal()) { + AccessInfo flags = f.getAccessFlags(); + if (flags.isStatic() && flags.isFinal()) { + LOG.debug("loadStaticValues(): Adding NULL initializer to static final field {}", f.getAlias()); f.addAttr(FieldInitAttr.NULL_VALUE); } } From d346ed0570dcaea21d018877273e7fe6f640cc98 Mon Sep 17 00:00:00 2001 From: S-trace Date: Thu, 2 Jan 2020 21:30:40 +0300 Subject: [PATCH 06/25] core: MethodNode: Fix possible decompilation failure on refresh inner class checkInstructions() may fail with NPE: ERROR - NullPointerException in pass: BlockSplitter in method: com.google.common.primitives.Ints.IntArrayAsList.(int[], int, int):void, dex: out.dex java.lang.NullPointerException: null at jadx.core.dex.nodes.MethodNode.checkInstructions(MethodNode.java:159) at jadx.core.dex.visitors.blocksmaker.BlockSplitter.visit(BlockSplitter.java:49) at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:31) at jadx.core.dex.visitors.DepthTraversal.lambda$visit$1(DepthTraversal.java:16) at java.util.ArrayList.forEach(ArrayList.java:1257) at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:16) at jadx.core.dex.visitors.DepthTraversal.lambda$visit$0(DepthTraversal.java:15) at java.util.ArrayList.forEach(ArrayList.java:1257) at jadx.core.dex.visitors.DepthTraversal.visit(DepthTraversal.java:15) at jadx.core.ProcessClass.process(ProcessClass.java:41) at jadx.core.ProcessClass.generateCode(ProcessClass.java:58) at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:292) at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:271) at jadx.core.dex.nodes.ClassNode.refresh(ClassNode.java:303) at jadx.api.JavaClass.refresh(JavaClass.java:61) at jadx.gui.treemodel.JClass.refresh(JClass.java:63) ... This happens because MethodNode.unloadInsnArr() call from BlockSplitter.visit() - after it instructions[] become null. So, try to reload method before processing its instructions array. --- .../src/main/java/jadx/core/dex/nodes/MethodNode.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 06e11ec2d..14c287b4e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -154,6 +154,15 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode { public void checkInstructions() { List list = new ArrayList<>(); + if (instructions == null) { + LOG.debug("Instructions == null, reloading method {}.{}", getClass().getName(), getName()); + unload(); + try { + load(); + } catch (DecodeException e) { + throw new JadxRuntimeException("Failed to reload method " + getClass().getName() + "." + getName()); + } + } for (InsnNode insnNode : instructions) { if (insnNode == null) { continue; From 15953f832f8863cdd141e04f81c872448be9701b Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 3 Jan 2020 00:48:06 +0300 Subject: [PATCH 07/25] core: Do not call addConstField() on class refresh - fix constants replacing There is a duplicate control in the ConstStorage.ValueStorage.add() method, so each constant should be added only once, and not be added on class refresh. Fixes "Replace constants" failure after renaming any node. --- .../src/main/java/jadx/core/dex/info/ConstStorage.java | 6 ++++-- .../src/main/java/jadx/core/dex/nodes/ClassNode.java | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index fbd706150..5e19520d3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -64,7 +64,7 @@ public class ConstStorage { this.replaceEnabled = args.isReplaceConsts(); } - public void processConstFields(ClassNode cls, List staticFields) { + public void processConstFields(ClassNode cls, List staticFields, boolean isRefresh) { if (!replaceEnabled || staticFields.isEmpty()) { return; } @@ -76,7 +76,9 @@ public class ConstStorage { && fv.getValue() != null && fv.getValueType() == FieldInitAttr.InitType.CONST && fv != FieldInitAttr.NULL_VALUE) { - addConstField(cls, f, fv.getValue(), accFlags.isPublic()); + if (!isRefresh) { + addConstField(cls, f, fv.getValue(), accFlags.isPublic()); + } } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 9e92e69a0..7968f7a04 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -102,7 +102,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { for (Field f : clsData.getStaticFields()) { fields.add(new FieldNode(this, f)); } - loadStaticValues(cls, fields); + loadStaticValues(cls, fields, false); for (Field f : clsData.getInstanceFields()) { fields.add(new FieldNode(this, f)); } @@ -169,7 +169,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { } } - private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { + private void loadStaticValues(ClassDef cls, List staticFields, boolean isRefresh) throws DecodeException { for (FieldNode f : staticFields) { AccessInfo flags = f.getAccessFlags(); if (flags.isStatic() && flags.isFinal()) { @@ -186,7 +186,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { parser.processFields(staticFields); // process const fields - root().getConstValues().processConstFields(this, staticFields); + root().getConstValues().processConstFields(this, staticFields, isRefresh); } private void parseClassSignature() { @@ -316,7 +316,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private void loadStaticInfo() { try { if (cls != null) { - loadStaticValues(cls, fields); + loadStaticValues(cls, fields, true); } } catch (DecodeException e) { LOG.error("Got DecodeException in loadStaticValues() for class {}", getRawName()); From 6d4caca6cc46019ce66469171b918aa16f62b59e Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 3 Jan 2020 01:52:52 +0300 Subject: [PATCH 08/25] core: ClassModifier: Don't skip methods with SKIP_FIRST_ARG attr Skipping those methods on class refresh leads to "M.this = r1;" like assigments appears in the inner class constructors. Unsure is this saint or not. --- .../src/main/java/jadx/core/dex/visitors/ClassModifier.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 508adb3e3..5518f4e82 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -105,7 +105,7 @@ public class ClassModifier extends AbstractVisitor { return false; } List args = mth.getArgRegs(); - if (args.isEmpty() || mth.contains(AFlag.SKIP_FIRST_ARG)) { + if (args.isEmpty()) { return false; } RegisterArg arg = args.get(0); From 1e9b28b369e299e31a64a7f47e864d34d846ca4a Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 3 Jan 2020 02:33:58 +0300 Subject: [PATCH 09/25] core: AType: Add FIELD_INIT and SOURCE_FILE to SKIP_ON_UNLOAD set Fixes disappearing litheral values for replaced constants in switches and source file names.. --- jadx-core/src/main/java/jadx/core/dex/attributes/AType.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index a18d953ab..5fb276d45 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -90,5 +90,7 @@ public class AType { RENAME_REASON, JADX_WARN, JADX_ERROR, + FIELD_INIT, + SOURCE_FILE, SKIP_MTH_ARGS)); } From 610f531653fcfa7042de2f53c69d5a102ed91102 Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 3 Jan 2020 03:34:49 +0300 Subject: [PATCH 10/25] core: EnumVisitor: Do not remove ACC_ENUM access flag This flag is necessary for class refresh, and should be left there. Fixes disappearing of enum fields after class refresh. --- .../src/main/java/jadx/core/dex/visitors/EnumVisitor.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index d016a37c9..88868f35e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -5,8 +5,6 @@ import java.util.List; import org.jetbrains.annotations.Nullable; -import com.android.dx.rop.code.AccessFlags; - import jadx.core.codegen.TypeGen; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; @@ -47,8 +45,7 @@ public class EnumVisitor extends AbstractVisitor { if (!convertToEnum(cls)) { AccessInfo accessFlags = cls.getAccessFlags(); if (accessFlags.isEnum()) { - cls.setAccessFlags(accessFlags.remove(AccessFlags.ACC_ENUM)); - cls.addAttr(AType.COMMENTS, "'enum' modifier removed"); + cls.addAttr(AType.COMMENTS, "'enum' modifier should be removed"); } } return true; From c72f2a2c9624194b81573b910dd7de59b8f5305e Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 3 Jan 2020 03:46:30 +0300 Subject: [PATCH 11/25] core: RenameReasonAttr: Do not append new reason if it is already there Fixes possible "reason: invalid class name and invalid class name" comments after class refresh. --- .../java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java index 028182295..b56dd409c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java @@ -46,7 +46,7 @@ public class RenameReasonAttr implements IAttribute { public RenameReasonAttr append(String reason) { if (description.isEmpty()) { description += reason; - } else { + } else if (!description.contains(reason)) { description += " and " + reason; } return this; From 17cbb3eab0a725b7c9946ceda54dbeb9c639b29d Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 3 Jan 2020 04:16:22 +0300 Subject: [PATCH 12/25] core: Fix possible NPE in DebugInfoParser.addrChange() This may happen because MethodNode.unloadInsnArr() call from BlockSplitter.visit() - after it instructions[] become null. So, try to reload method before processing its instructions array and bail if insnArr still null even after reloading method. --- .../java/jadx/core/dex/nodes/MethodNode.java | 18 +++++++++++------- .../debuginfo/DebugInfoParseVisitor.java | 9 +++++++++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 14c287b4e..889f2ff2c 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -155,13 +155,8 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode { public void checkInstructions() { List list = new ArrayList<>(); if (instructions == null) { - LOG.debug("Instructions == null, reloading method {}.{}", getClass().getName(), getName()); - unload(); - try { - load(); - } catch (DecodeException e) { - throw new JadxRuntimeException("Failed to reload method " + getClass().getName() + "." + getName()); - } + LOG.debug("instructions == null, reloading method {}.{}", getClass().getName(), getName()); + reload(); } for (InsnNode insnNode : instructions) { if (insnNode == null) { @@ -182,6 +177,15 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode { } } + public void reload() { + unload(); + try { + load(); + } catch (DecodeException e) { + throw new JadxRuntimeException("Failed to reload method " + getClass().getName() + "." + getName()); + } + } + public void initMethodTypes() { List types = parseSignature(); if (types == null) { diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java index 3bc4368ae..e21b5734d 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java @@ -48,6 +48,15 @@ public class DebugInfoParseVisitor extends AbstractVisitor { private void processDebugInfo(MethodNode mth, int debugOffset) { InsnNode[] insnArr = mth.getInstructions(); + if (insnArr == null) { + LOG.debug("insnArr == null, reloading method {}.{}", getClass().getName(), mth.getName()); + mth.reload(); + insnArr = mth.getInstructions(); + } + if (insnArr == null) { + LOG.error("insnArr == null even after reloading method {}.{} - bailing", getClass().getName(), mth.getName()); + return; + } DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr); List localVars = debugInfoParser.process(); attachDebugInfo(mth, localVars, insnArr); From 1eca2b6cb0163e696c3f524bdba9c22b79dc8fe7 Mon Sep 17 00:00:00 2001 From: S-trace Date: Fri, 3 Jan 2020 05:29:32 +0300 Subject: [PATCH 13/25] core: ClassInfo: Do not ignore setting alias to original class name Fixes trouble with renaming class back to its original name. --- jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index 7e166566f..ae95d6c6b 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -66,11 +66,9 @@ public final class ClassInfo implements Comparable { } public void changeShortName(String aliasName) { - if (!Objects.equals(name, aliasName)) { - ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName); - fillAliasFullName(newAlias); - this.alias = newAlias; - } + ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName); + fillAliasFullName(newAlias); + this.alias = newAlias; } public void changePkg(String aliasPkg) { From 0c4b807caa7fa110dcfc06809d50f8ce519cfbbf Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 3 Jan 2020 20:39:54 +0300 Subject: [PATCH 14/25] fix: improve ClassNode reloading and revert some changes --- .../src/main/java/jadx/api/ICodeCache.java | 2 - .../src/main/java/jadx/api/JavaClass.java | 5 +- .../java/jadx/api/impl/InMemoryCodeCache.java | 5 -- .../java/jadx/api/impl/NoOpCodeCache.java | 4 -- .../java/jadx/core/dex/attributes/AType.java | 6 --- .../attributes/nodes/RenameReasonAttr.java | 2 +- .../java/jadx/core/dex/info/ConstStorage.java | 6 +-- .../java/jadx/core/dex/nodes/ClassNode.java | 51 ++++++------------- .../java/jadx/core/dex/nodes/MethodNode.java | 4 -- .../jadx/core/dex/visitors/ClassModifier.java | 2 +- .../jadx/core/dex/visitors/EnumVisitor.java | 5 +- .../debuginfo/DebugInfoParseVisitor.java | 9 ---- 12 files changed, 26 insertions(+), 75 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/ICodeCache.java b/jadx-core/src/main/java/jadx/api/ICodeCache.java index bc121067b..a078f79cf 100644 --- a/jadx-core/src/main/java/jadx/api/ICodeCache.java +++ b/jadx-core/src/main/java/jadx/api/ICodeCache.java @@ -8,6 +8,4 @@ public interface ICodeCache { @Nullable ICodeInfo get(String clsFullName); - - void remove(String clsFullName); } diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 67d4aba53..8227771da 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -57,8 +57,9 @@ public final class JavaClass implements JavaNode { cls.decompile(); } - public void refresh() { - cls.refresh(); + public synchronized void refresh() { + listsLoaded = false; + cls.reloadCode(); } public synchronized String getSmali() { diff --git a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java index 8f36b4c0e..04d80fef8 100644 --- a/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/InMemoryCodeCache.java @@ -21,9 +21,4 @@ public class InMemoryCodeCache implements ICodeCache { public @Nullable ICodeInfo get(String clsFullName) { return storage.get(clsFullName); } - - @Override - public void remove(String clsFullName) { - storage.remove(clsFullName); - } } diff --git a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java index cf738372c..f764cde4e 100644 --- a/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java +++ b/jadx-core/src/main/java/jadx/api/impl/NoOpCodeCache.java @@ -16,8 +16,4 @@ public class NoOpCodeCache implements ICodeCache { public @Nullable ICodeInfo get(String clsFullName) { return null; } - - @Override - public void remove(String clsFullName) { - } } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java index 5fb276d45..2cdab5b73 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AType.java @@ -86,11 +86,5 @@ public class AType { public static final Set> SKIP_ON_UNLOAD = new HashSet<>(Arrays.asList( FIELD_REPLACE, METHOD_INLINE, - COMMENTS, - RENAME_REASON, - JADX_WARN, - JADX_ERROR, - FIELD_INIT, - SOURCE_FILE, SKIP_MTH_ARGS)); } diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java index b56dd409c..028182295 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/nodes/RenameReasonAttr.java @@ -46,7 +46,7 @@ public class RenameReasonAttr implements IAttribute { public RenameReasonAttr append(String reason) { if (description.isEmpty()) { description += reason; - } else if (!description.contains(reason)) { + } else { description += " and " + reason; } return this; diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index 5e19520d3..fbd706150 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -64,7 +64,7 @@ public class ConstStorage { this.replaceEnabled = args.isReplaceConsts(); } - public void processConstFields(ClassNode cls, List staticFields, boolean isRefresh) { + public void processConstFields(ClassNode cls, List staticFields) { if (!replaceEnabled || staticFields.isEmpty()) { return; } @@ -76,9 +76,7 @@ public class ConstStorage { && fv.getValue() != null && fv.getValueType() == FieldInitAttr.InitType.CONST && fv != FieldInitAttr.NULL_VALUE) { - if (!isRefresh) { - addConstField(cls, f, fv.getValue(), accFlags.isPublic()); - } + addConstField(cls, f, fv.getValue(), accFlags.isPublic()); } } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 7968f7a04..00f3b0390 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -54,8 +54,8 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private List interfaces; private List generics = Collections.emptyList(); - private final List methods; - private final List fields; + private List methods; + private List fields; private List innerClasses = Collections.emptyList(); // store smali @@ -74,6 +74,10 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { this.cls = cls; this.clsDefOffset = cls.getOffset(); this.clsInfo = ClassInfo.fromDex(dex, cls.getTypeIndex()); + initialLoad(); + } + + private void initialLoad() { try { if (cls.getSupertypeIndex() == DexNode.NO_INDEX) { this.superClass = null; @@ -102,7 +106,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { for (Field f : clsData.getStaticFields()) { fields.add(new FieldNode(this, f)); } - loadStaticValues(cls, fields, false); + loadStaticValues(cls, fields); for (Field f : clsData.getInstanceFields()) { fields.add(new FieldNode(this, f)); } @@ -169,7 +173,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { } } - private void loadStaticValues(ClassDef cls, List staticFields, boolean isRefresh) throws DecodeException { + private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { for (FieldNode f : staticFields) { AccessInfo flags = f.getAccessFlags(); if (flags.isStatic() && flags.isFinal()) { @@ -186,7 +190,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { parser.processFields(staticFields); // process const fields - root().getConstValues().processConstFields(this, staticFields, isRefresh); + root().getConstValues().processConstFields(this, staticFields); } private void parseClassSignature() { @@ -275,7 +279,11 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { return decompile(true); } - public ICodeInfo reloadCode() { + public synchronized ICodeInfo reloadCode() { + unload(); + clearAttributes(); + initialLoad(); + load(); return decompile(false); } @@ -294,36 +302,6 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { return codeInfo; } - public synchronized ICodeInfo refresh() { - reloadRecursive(); - return decompile(false); - } - - private void reloadRecursive() { - load(); - int sfIdx = cls.getSourceFileIndex(); - if (sfIdx != DexNode.NO_INDEX) { - String fileName = dex.getString(sfIdx); - addSourceFilenameAttr(fileName); - } - for (ClassNode innerCls : getInnerClasses()) { - innerCls.reloadRecursive(); - } - loadStaticInfo(); - loadAnnotations(cls); - } - - private void loadStaticInfo() { - try { - if (cls != null) { - loadStaticValues(cls, fields, true); - } - } catch (DecodeException e) { - LOG.error("Got DecodeException in loadStaticValues() for class {}", getRawName()); - e.printStackTrace(); - } - } - @Override public void load() { for (MethodNode mth : getMethods()) { @@ -346,6 +324,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { fields.forEach(FieldNode::unloadAttributes); unloadAttributes(); setState(NOT_LOADED); + this.smali = null; } private void buildCache() { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java index 889f2ff2c..5006bb30e 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/MethodNode.java @@ -154,10 +154,6 @@ public class MethodNode extends LineAttrNode implements ILoadable, ICodeNode { public void checkInstructions() { List list = new ArrayList<>(); - if (instructions == null) { - LOG.debug("instructions == null, reloading method {}.{}", getClass().getName(), getName()); - reload(); - } for (InsnNode insnNode : instructions) { if (insnNode == null) { continue; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java index 5518f4e82..508adb3e3 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ClassModifier.java @@ -105,7 +105,7 @@ public class ClassModifier extends AbstractVisitor { return false; } List args = mth.getArgRegs(); - if (args.isEmpty()) { + if (args.isEmpty() || mth.contains(AFlag.SKIP_FIRST_ARG)) { return false; } RegisterArg arg = args.get(0); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java index 88868f35e..d016a37c9 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/EnumVisitor.java @@ -5,6 +5,8 @@ import java.util.List; import org.jetbrains.annotations.Nullable; +import com.android.dx.rop.code.AccessFlags; + import jadx.core.codegen.TypeGen; import jadx.core.deobf.NameMapper; import jadx.core.dex.attributes.AFlag; @@ -45,7 +47,8 @@ public class EnumVisitor extends AbstractVisitor { if (!convertToEnum(cls)) { AccessInfo accessFlags = cls.getAccessFlags(); if (accessFlags.isEnum()) { - cls.addAttr(AType.COMMENTS, "'enum' modifier should be removed"); + cls.setAccessFlags(accessFlags.remove(AccessFlags.ACC_ENUM)); + cls.addAttr(AType.COMMENTS, "'enum' modifier removed"); } } return true; diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java index e21b5734d..3bc4368ae 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/debuginfo/DebugInfoParseVisitor.java @@ -48,15 +48,6 @@ public class DebugInfoParseVisitor extends AbstractVisitor { private void processDebugInfo(MethodNode mth, int debugOffset) { InsnNode[] insnArr = mth.getInstructions(); - if (insnArr == null) { - LOG.debug("insnArr == null, reloading method {}.{}", getClass().getName(), mth.getName()); - mth.reload(); - insnArr = mth.getInstructions(); - } - if (insnArr == null) { - LOG.error("insnArr == null even after reloading method {}.{} - bailing", getClass().getName(), mth.getName()); - return; - } DebugInfoParser debugInfoParser = new DebugInfoParser(mth, debugOffset, insnArr); List localVars = debugInfoParser.process(); attachDebugInfo(mth, localVars, insnArr); From 9dbffef14044c78461f29cbee09bf1521eecbab9 Mon Sep 17 00:00:00 2001 From: Skylot Date: Sun, 5 Jan 2020 17:28:25 +0300 Subject: [PATCH 15/25] fix: deep reload for inner classes, const values and anonymous classes --- .../java/jadx/core/dex/info/ClassInfo.java | 10 +++-- .../java/jadx/core/dex/info/ConstStorage.java | 19 ++++++++ .../java/jadx/core/dex/nodes/ClassNode.java | 20 ++++++--- .../core/dex/visitors/ProcessAnonymous.java | 19 ++++---- .../rename/TestAnonymousInline.java | 34 ++++++++++++++ .../integration/rename/TestConstReplace.java | 30 +++++++++++++ .../integration/rename/TestRenameEnum.java | 44 +++++++++++++++++++ 7 files changed, 157 insertions(+), 19 deletions(-) create mode 100644 jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/rename/TestConstReplace.java create mode 100644 jadx-core/src/test/java/jadx/tests/integration/rename/TestRenameEnum.java diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index ae95d6c6b..7e9858cb4 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -66,9 +66,13 @@ public final class ClassInfo implements Comparable { } public void changeShortName(String aliasName) { - ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName); - fillAliasFullName(newAlias); - this.alias = newAlias; + if (!Objects.equals(name, aliasName)) { + ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName); + fillAliasFullName(newAlias); + this.alias = newAlias; + } else { + this.alias = null; + } } public void changePkg(String aliasPkg) { diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index fbd706150..d599d83bc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -2,8 +2,10 @@ package jadx.core.dex.info; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import org.jetbrains.annotations.Nullable; @@ -52,6 +54,18 @@ public class ConstStorage { public boolean contains(Object value) { return duplicates.contains(value) || values.containsKey(value); } + + void removeForCls(ClassNode cls) { + Iterator> it = values.entrySet().iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + FieldNode field = entry.getValue(); + if (field.getParentClass().equals(cls)) { + it.remove(); + duplicates.remove(entry.getKey()); + } + } + } } private final boolean replaceEnabled; @@ -82,6 +96,11 @@ public class ConstStorage { } } + public void removeForClass(ClassNode cls) { + classes.remove(cls); + globalValues.removeForCls(cls); + } + private void addConstField(ClassNode cls, FieldNode fld, Object value, boolean isPublic) { if (isPublic) { globalValues.put(value, fld); diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 00f3b0390..62d693d29 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -35,6 +35,7 @@ import jadx.core.dex.nodes.parser.AnnotationsParser; import jadx.core.dex.nodes.parser.FieldInitAttr; import jadx.core.dex.nodes.parser.SignatureParser; import jadx.core.dex.nodes.parser.StaticValuesParser; +import jadx.core.dex.visitors.ProcessAnonymous; import jadx.core.utils.SmaliUtils; import jadx.core.utils.exceptions.DecodeException; import jadx.core.utils.exceptions.JadxRuntimeException; @@ -175,9 +176,7 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { private void loadStaticValues(ClassDef cls, List staticFields) throws DecodeException { for (FieldNode f : staticFields) { - AccessInfo flags = f.getAccessFlags(); - if (flags.isStatic() && flags.isFinal()) { - LOG.debug("loadStaticValues(): Adding NULL initializer to static final field {}", f.getAlias()); + if (f.getAccessFlags().isFinal()) { f.addAttr(FieldInitAttr.NULL_VALUE); } } @@ -281,12 +280,21 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { public synchronized ICodeInfo reloadCode() { unload(); - clearAttributes(); - initialLoad(); - load(); + deepUnload(); return decompile(false); } + private void deepUnload() { + clearAttributes(); + root().getConstValues().removeForClass(this); + initialLoad(); + ProcessAnonymous.runForClass(this); + + for (ClassNode innerClass : innerClasses) { + innerClass.deepUnload(); + } + } + private synchronized ICodeInfo decompile(boolean searchInCache) { ICodeCache codeCache = root().getCodeCache(); ClassNode topParentClass = getTopParentClass(); diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java index faa66b823..749e5eaca 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/ProcessAnonymous.java @@ -5,27 +5,29 @@ import jadx.core.dex.nodes.ClassNode; import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.RootNode; -import jadx.core.dex.visitors.regions.RegionMakerVisitor; @JadxVisitor( name = "ProcessAnonymous", - desc = "Mark anonymous and lambda classes (for future inline)", - runAfter = RegionMakerVisitor.class + desc = "Mark anonymous and lambda classes (for future inline)" ) public class ProcessAnonymous extends AbstractVisitor { @Override public void init(RootNode root) { - if (!root.getArgs().isInlineAnonymousClasses()) { - return; + if (root.getArgs().isInlineAnonymousClasses()) { + for (ClassNode cls : root.getClasses(true)) { + markAnonymousClass(cls); + } } + } - for (ClassNode cls : root.getClasses(true)) { + public static void runForClass(ClassNode cls) { + if (cls.root().getArgs().isInlineAnonymousClasses()) { markAnonymousClass(cls); } } - private static boolean markAnonymousClass(ClassNode cls) { + private static void markAnonymousClass(ClassNode cls) { if (isAnonymous(cls) || isLambdaCls(cls)) { cls.add(AFlag.ANONYMOUS_CLASS); cls.add(AFlag.DONT_GENERATE); @@ -35,9 +37,7 @@ public class ProcessAnonymous extends AbstractVisitor { mth.add(AFlag.ANONYMOUS_CONSTRUCTOR); } } - return true; } - return false; } private static boolean isAnonymous(ClassNode cls) { @@ -62,5 +62,4 @@ public class ProcessAnonymous extends AbstractVisitor { } return c; } - } diff --git a/jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java b/jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java new file mode 100644 index 000000000..712264884 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java @@ -0,0 +1,34 @@ +package jadx.tests.integration.rename; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestAnonymousInline extends IntegrationTest { + + public static class TestCls { + public Runnable test() { + return new Runnable() { + @Override + public void run() { + System.out.println("run"); + } + }; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + assertThat(cls.getCode()) + .containsOnlyOnce("return new Runnable() {"); + + assertThat(cls.reloadCode()) + .print() + .containsOnlyOnce("return new Runnable() {") + .doesNotContain("AnonymousClass1"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/rename/TestConstReplace.java b/jadx-core/src/test/java/jadx/tests/integration/rename/TestConstReplace.java new file mode 100644 index 000000000..378fc28c0 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/rename/TestConstReplace.java @@ -0,0 +1,30 @@ +package jadx.tests.integration.rename; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestConstReplace extends IntegrationTest { + + public static class TestCls { + public static final String CONST = "SOME_CONST"; + + public String test() { + return CONST; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + assertThat(cls.getCode()) + .containsOnlyOnce("return CONST;"); + + assertThat(cls.reloadCode()) + .print() + .containsOnlyOnce("return CONST;"); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/integration/rename/TestRenameEnum.java b/jadx-core/src/test/java/jadx/tests/integration/rename/TestRenameEnum.java new file mode 100644 index 000000000..2ae89b18a --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/integration/rename/TestRenameEnum.java @@ -0,0 +1,44 @@ +package jadx.tests.integration.rename; + +import org.junit.jupiter.api.Test; + +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.api.IntegrationTest; + +import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat; + +public class TestRenameEnum extends IntegrationTest { + + public static class TestCls { + + public enum A implements Runnable { + ONE { + @Override + public void run() { + System.out.println("ONE"); + } + }, + TWO { + @Override + public void run() { + System.out.println("TWO"); + } + }; + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + assertThat(cls.getCode()) + .containsOnlyOnce("public enum A ") + .containsOnlyOnce("ONE {"); + + cls.getInnerClasses().get(0).getClassInfo().changeShortName("ARenamed"); + + assertThat(cls.reloadCode()) + .print() + .containsOnlyOnce("public enum ARenamed ") + .containsOnlyOnce("ONE {"); + } +} From 77fc6435a06523835488e1edb84343226aaa739f Mon Sep 17 00:00:00 2001 From: S-trace Date: Thu, 16 Jan 2020 21:02:04 +0300 Subject: [PATCH 16/25] core: ConstStorage: Don't put known duplicate value to ValueStorage.values map This is a microoptimization, which remove unnecessary values.put() and values.remove() pair of operations if ValueStorage.put() is called for a known duplicated value. --- .../src/main/java/jadx/core/dex/info/ConstStorage.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index cbb30286a..938c5f8ee 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -38,16 +38,16 @@ public class ConstStorage { * @return true if this value is duplicated */ public boolean put(Object value, FieldNode fld) { + if (duplicates.contains(value)) { + values.remove(value); + return true; + } FieldNode prev = values.put(value, fld); if (prev != null) { values.remove(value); duplicates.add(value); return true; } - if (duplicates.contains(value)) { - values.remove(value); - return true; - } return false; } From a0e13d0481be7c7fd0c9df7abdfa0d74c2be66ef Mon Sep 17 00:00:00 2001 From: S-trace Date: Thu, 16 Jan 2020 23:53:22 +0300 Subject: [PATCH 17/25] gui: RenameDialog: Rename tmp deobf map file too Fixes /tmp/deobf_tmp_*.txt temporary files accumulation on renames. --- jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index 9d84be936..c1703bed1 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -132,6 +132,7 @@ public class RenameDialog extends JDialog { Files.copy(deobfMapPath, oldMap.toPath(), StandardCopyOption.REPLACE_EXISTING); Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING); Files.delete(oldMap.toPath()); + Files.delete(tmpFile.toPath()); return true; } From 265a78cd2326269195402562a833247d0e06be91 Mon Sep 17 00:00:00 2001 From: S-trace Date: Thu, 16 Jan 2020 23:56:13 +0300 Subject: [PATCH 18/25] core: ConstStorage: Do not remove values from duplicates set in removeForCls() If the constant already got duplicates - it will have duplicates even after class reload, won't it? But removing this constant from duplicates may break constants replacing (just imagine a class TestClass with public static final int TEST_CONSTANT = 1; - after reloading TestClass each "(int) 1" litheral will be replaced to "TEST_CONSTANT" reference in each reloaded class and trivial increments will become werid expressions (int y = x + 1; will become int y = x + TestClass.TEST_CONSTANT;)). --- jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java | 1 - 1 file changed, 1 deletion(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index 938c5f8ee..ca9db0171 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -62,7 +62,6 @@ public class ConstStorage { FieldNode field = entry.getValue(); if (field.getParentClass().equals(cls)) { it.remove(); - duplicates.remove(entry.getKey()); } } } From 467403362d1819acc96663e52dbeb2994f76aea4 Mon Sep 17 00:00:00 2001 From: S-trace Date: Thu, 16 Jan 2020 23:59:36 +0300 Subject: [PATCH 19/25] core: ConstStorage: Use ConcurrentHashMap for values map in ValueStorage Exception in thread "pool-9-thread-7" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445) at java.util.HashMap$EntryIterator.next(HashMap.java:1479) at java.util.HashMap$EntryIterator.next(HashMap.java:1477) at jadx.core.dex.info.ConstStorage$ValueStorage.removeForCls(ConstStorage.java:61) at jadx.core.dex.info.ConstStorage.removeForClass(ConstStorage.java:100) at jadx.core.dex.nodes.ClassNode.deepUnload(ClassNode.java:290) at jadx.core.dex.nodes.ClassNode.deepUnload(ClassNode.java:295) at jadx.core.dex.nodes.ClassNode.reloadCode(ClassNode.java:284) at jadx.api.JavaClass.refresh(JavaClass.java:62) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) --- jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index ca9db0171..6385ec5da 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import org.jetbrains.annotations.Nullable; @@ -23,7 +24,7 @@ import jadx.core.utils.ErrorsCounter; public class ConstStorage { private static final class ValueStorage { - private final Map values = new HashMap<>(); + private final Map values = new ConcurrentHashMap<>(); private final Set duplicates = new HashSet<>(); public Map getValues() { From bb0fad28349a655b4806c903413192cb6b39b719 Mon Sep 17 00:00:00 2001 From: Skylot Date: Fri, 17 Jan 2020 16:30:40 +0300 Subject: [PATCH 20/25] fix: resolve multi-threaded unloading --- jadx-core/src/main/java/jadx/api/JavaClass.java | 2 +- .../src/main/java/jadx/core/dex/nodes/ClassNode.java | 10 +++++++++- jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 8227771da..84470e89d 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -59,7 +59,7 @@ public final class JavaClass implements JavaNode { public synchronized void refresh() { listsLoaded = false; - cls.reloadCode(); + cls.reRunDecompile(); } public synchronized String getSmali() { diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index ab81f0cfd..51e6cc486 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -279,13 +279,21 @@ public class ClassNode extends LineAttrNode implements ILoadable, ICodeNode { return decompile(true); } + public synchronized ICodeInfo reRunDecompile() { + return decompile(false); + } + public synchronized ICodeInfo reloadCode() { unload(); deepUnload(); return decompile(false); } - private void deepUnload() { + public void deepUnload() { + if (cls == null) { + // manually added class + return; + } clearAttributes(); root().getConstValues().removeForClass(this); initialLoad(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index c1703bed1..b6d169ac6 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -222,6 +222,10 @@ public class RenameDialog extends JDialog { refreshTabs(mainWindow.getTabbedPane(), updatedClasses); if (updatedClasses.size() > 0) { + for (JavaClass updatedClass : updatedClasses) { + updatedClass.unload(); + updatedClass.getClassNode().deepUnload(); + } setRefreshTask(updatedClasses); } From 0b6fabbc718921379e6b4cdac2cde85219d0b06f Mon Sep 17 00:00:00 2001 From: S-trace Date: Sat, 18 Jan 2020 05:23:21 +0300 Subject: [PATCH 21/25] gui: Perform classes unload in the background UnloadJob This should improve interface responsibility if there are many classes to refresh after rename. --- .../java/jadx/gui/jobs/BackgroundWorker.java | 21 ++++++++++--- .../main/java/jadx/gui/jobs/UnloadJob.java | 31 +++++++++++++++++++ .../src/main/java/jadx/gui/ui/MainWindow.java | 2 +- .../main/java/jadx/gui/ui/RenameDialog.java | 16 +++++----- .../main/java/jadx/gui/utils/CacheObject.java | 10 ++++++ 5 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 jadx-gui/src/main/java/jadx/gui/jobs/UnloadJob.java diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java index 765e9a750..5f4c04f79 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java @@ -54,11 +54,22 @@ public class BackgroundWorker extends SwingWorker { runJob(cache.getDecompileJob()); LOG.debug("Memory usage: After decompile: {}", UiUtils.memoryInfo()); - LOG.info("Memory usage: Before refresh: {}", UiUtils.memoryInfo()); - long start = System.nanoTime(); - runJob(cache.getRefreshJob()); - cache.setRefreshJob(null); - LOG.info("Memory usage: After refresh: {}, refresh took {} ms", UiUtils.memoryInfo(), (System.nanoTime() - start) / 1000000); + if (cache.getUnloadJob() != null) { + LOG.info("Memory usage: Before unload: {}", UiUtils.memoryInfo()); + long start = System.nanoTime(); + runJob(cache.getUnloadJob()); + cache.setUnloadJob(null); + LOG.info("Memory usage: After unload: {}, unload took {} ms", UiUtils.memoryInfo(), (System.nanoTime() - start) / 1000000); + } + + if (cache.getRefreshJob() != null) { + LOG.info("Memory usage: Before refresh: {}", UiUtils.memoryInfo()); + long start = System.nanoTime(); + runJob(cache.getRefreshJob()); + cache.setRefreshJob(null); + LOG.info("Memory usage: After refresh: {}, refresh took {} ms", UiUtils.memoryInfo(), + (System.nanoTime() - start) / 1000000); + } LOG.debug("Memory usage: Before index: {}", UiUtils.memoryInfo()); runJob(cache.getIndexJob()); diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/UnloadJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/UnloadJob.java new file mode 100644 index 000000000..e3c646d6e --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/jobs/UnloadJob.java @@ -0,0 +1,31 @@ +package jadx.gui.jobs; + +import java.util.Set; + +import jadx.api.JavaClass; +import jadx.gui.JadxWrapper; + +public class UnloadJob extends BackgroundJob { + + private Set refreshClasses; + + public UnloadJob(JadxWrapper jadxWrapper, int threadsCount, Set refreshClasses) { + super(jadxWrapper, threadsCount); + this.refreshClasses = refreshClasses; + } + + protected void runJob() { + for (final JavaClass cls : refreshClasses) { + addTask(() -> { + cls.unload(); + cls.getClassNode().deepUnload(); + }); + } + } + + @Override + public String getInfoString() { + return "Refreshing: "; + } + +} 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 e9aecfa23..0276aa7ba 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -415,7 +415,7 @@ public class MainWindow extends JFrame { } } - synchronized void runBackgroundRefreshAndIndexJobs() { + synchronized void runBackgroundUnloadRefreshAndIndexJobs() { cancelBackgroundJobs(); backgroundWorker = new BackgroundWorker(cacheObject, progressPane); backgroundWorker.exec(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index b6d169ac6..983efa97a 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -30,6 +30,7 @@ import jadx.core.dex.visitors.RenameVisitor; import jadx.core.utils.files.InputFile; import jadx.gui.jobs.IndexJob; import jadx.gui.jobs.RefreshJob; +import jadx.gui.jobs.UnloadJob; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; @@ -39,7 +40,6 @@ import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.ui.codearea.CodeArea; import jadx.gui.ui.codearea.CodePanel; import jadx.gui.utils.CacheObject; -import jadx.gui.utils.CodeUsageInfo; import jadx.gui.utils.NLS; import jadx.gui.utils.TextStandardActions; @@ -222,10 +222,6 @@ public class RenameDialog extends JDialog { refreshTabs(mainWindow.getTabbedPane(), updatedClasses); if (updatedClasses.size() > 0) { - for (JavaClass updatedClass : updatedClasses) { - updatedClass.unload(); - updatedClass.getClassNode().deepUnload(); - } setRefreshTask(updatedClasses); } @@ -268,19 +264,21 @@ public class RenameDialog extends JDialog { private void setRefreshTask(Set refreshClasses) { CacheObject cache = mainWindow.getCacheObject(); + UnloadJob unloadJob = new UnloadJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses); RefreshJob refreshJob = new RefreshJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses); - LOG.info("Waiting for old refreshJob"); - while (cache.getRefreshJob() != null) { + LOG.info("Waiting for old unloadJob and refreshJob"); + while (cache.getUnloadJob() != null || cache.getRefreshJob() != null) { try { Thread.sleep(10); } catch (InterruptedException e) { return; } } - LOG.info("Old refreshJob finished"); + LOG.info("Old unloadJob and refreshJob finished"); + cache.setUnloadJob(unloadJob); cache.setRefreshJob(refreshJob); cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), mainWindow.getCacheObject(), mainWindow.getSettings().getThreadsCount())); - mainWindow.runBackgroundRefreshAndIndexJobs(); + mainWindow.runBackgroundUnloadRefreshAndIndexJobs(); } private void initCommon() { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java index 4e523568c..b367114bb 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable; import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.IndexJob; import jadx.gui.jobs.RefreshJob; +import jadx.gui.jobs.UnloadJob; import jadx.gui.ui.SearchDialog; import jadx.gui.utils.search.TextSearchIndex; @@ -15,6 +16,7 @@ public class CacheObject { private DecompileJob decompileJob; private IndexJob indexJob; + private UnloadJob unloadJob; private RefreshJob refreshJob; private TextSearchIndex textIndex; @@ -99,4 +101,12 @@ public class CacheObject { public void setRefreshJob(RefreshJob refreshJob) { this.refreshJob = refreshJob; } + + public UnloadJob getUnloadJob() { + return unloadJob; + } + + public void setUnloadJob(UnloadJob unloadJob) { + this.unloadJob = unloadJob; + } } From d98321026dcfe72dbf1a9b379ce2aac8e1039960 Mon Sep 17 00:00:00 2001 From: S-trace Date: Sat, 18 Jan 2020 05:37:00 +0300 Subject: [PATCH 22/25] gui: RenameDialog: Unload classes in refreshTabs() before refreshing This should fix possible problems with incorrect refresh for open classes. --- jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index 983efa97a..9c15ef7ad 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -16,6 +16,7 @@ import java.util.Set; import javax.swing.*; +import jadx.gui.utils.CodeUsageInfo; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -237,6 +238,8 @@ public class RenameDialog extends JDialog { JavaClass javaClass = rootClass.getCls(); if (updatedClasses.contains(javaClass) || node.getRootClass().getCls() == javaClass) { LOG.info("Refreshing rootClass " + javaClass.getRawName()); + javaClass.unload(); + javaClass.getClassNode().deepUnload(); rootClass.refresh(); // Update code cache ClassCodeContentPanel codePanel = (ClassCodeContentPanel) contentPanel; CodePanel javaPanel = codePanel.getJavaCodePanel(); From f66ec9168cb308dee467b1c398f5eacb346265ab Mon Sep 17 00:00:00 2001 From: Skylot Date: Mon, 9 Mar 2020 19:34:54 +0000 Subject: [PATCH 23/25] test: update test TestAnonymousInline --- .../jadx/tests/api/utils/assertj/JadxCodeAssertions.java | 5 +++++ .../jadx/tests/integration/rename/TestAnonymousInline.java | 1 + 2 files changed, 6 insertions(+) diff --git a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java index adbe9d7d7..2c5c75bae 100644 --- a/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java +++ b/jadx-core/src/test/java/jadx/tests/api/utils/assertj/JadxCodeAssertions.java @@ -61,6 +61,11 @@ public class JadxCodeAssertions extends AbstractStringAssert return containsOnlyOnce(sb.toString()); } + public JadxCodeAssertions removeBlockComments() { + String code = actual.replaceAll("/\\*.*\\*/", ""); + return new JadxCodeAssertions(code); + } + public JadxCodeAssertions print() { System.out.println("-----------------------------------------------------------"); System.out.println(actual); diff --git a/jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java b/jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java index 712264884..f819046bd 100644 --- a/jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java +++ b/jadx-core/src/test/java/jadx/tests/integration/rename/TestAnonymousInline.java @@ -27,6 +27,7 @@ public class TestAnonymousInline extends IntegrationTest { .containsOnlyOnce("return new Runnable() {"); assertThat(cls.reloadCode()) + .removeBlockComments() // remove comment about inlined class .print() .containsOnlyOnce("return new Runnable() {") .doesNotContain("AnonymousClass1"); From 705ceca42a39f94c6fa3bdf63dbaae5bba616aa8 Mon Sep 17 00:00:00 2001 From: Soul Trace Date: Thu, 14 May 2020 22:56:35 +0300 Subject: [PATCH 24/25] fix(gui): RenameDialog: Warn user if deobfuscation settings are invalid (PR #935) The rename functionality relies on deobfuscation for now - so let the user know this and ask the user to enable deobfuscation to get rename working. The "Force rewrite deobfuscation map file" option effectively disables renaming, because renaming relies on deobfuscation map modification for now, but Force rewrite rewrites the map on each file reload, destroying changes. So. let the user know this issue instead of silent failure. --- .../main/java/jadx/gui/ui/RenameDialog.java | 54 +++++++++++++------ .../resources/i18n/Messages_de_DE.properties | 2 + .../resources/i18n/Messages_en_US.properties | 2 + .../resources/i18n/Messages_es_ES.properties | 2 + .../resources/i18n/Messages_zh_CN.properties | 2 + 5 files changed, 47 insertions(+), 15 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index c30171c96..5f3dd600d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -31,6 +31,7 @@ import jadx.core.utils.files.InputFile; import jadx.gui.jobs.IndexJob; import jadx.gui.jobs.RefreshJob; import jadx.gui.jobs.UnloadJob; +import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JField; import jadx.gui.treemodel.JMethod; @@ -39,10 +40,7 @@ import jadx.gui.treemodel.JPackage; import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.ui.codearea.CodeArea; import jadx.gui.ui.codearea.CodePanel; -import jadx.gui.utils.CacheObject; -import jadx.gui.utils.CodeUsageInfo; -import jadx.gui.utils.NLS; -import jadx.gui.utils.TextStandardActions; +import jadx.gui.utils.*; public class RenameDialog extends JDialog { private static final long serialVersionUID = -3269715644416902410L; @@ -62,8 +60,39 @@ public class RenameDialog extends JDialog { mainWindow = codeArea.getMainWindow(); this.codeArea = codeArea; this.node = node; - initUI(); - loadWindowPos(); + if (isDeobfuscationSettingsValid()) { + initUI(); + loadWindowPos(); + } else { + LOG.error("Deobfuscation settings are invalid - please enable deobfuscation and disable force rewrite deobfuscation map"); + } + } + + private boolean isDeobfuscationSettingsValid() { + boolean valid = true; + String errorMessage = null; + JadxSettings settings = mainWindow.getSettings(); + final LangLocale langLocale = settings.getLangLocale(); + if (settings.isDeobfuscationForceSave()) { + valid = false; + errorMessage = NLS.str("msg.rename_disabled_force_rewrite_enabled", langLocale); + } + if (!settings.isDeobfuscationOn()) { + valid = false; + errorMessage = NLS.str("msg.rename_disabled_deobfuscation_disabled", langLocale); + } + if (errorMessage != null) { + showRenameDisabledErrorMessage(langLocale, errorMessage); + } + return valid; + } + + private void showRenameDisabledErrorMessage(LangLocale langLocale, String message) { + JOptionPane.showMessageDialog( + mainWindow, + message, + NLS.str("msg.rename_disabled_title", langLocale), + JOptionPane.ERROR_MESSAGE); } private void loadWindowPos() { @@ -131,6 +160,7 @@ public class RenameDialog extends JDialog { fileOut.close(); File oldMap = File.createTempFile("deobf_bak_", ".txt"); Files.copy(deobfMapPath, oldMap.toPath(), StandardCopyOption.REPLACE_EXISTING); + LOG.trace("Copying " + tmpFile.toPath() + " to " + deobfMapPath); Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING); Files.delete(oldMap.toPath()); Files.delete(tmpFile.toPath()); @@ -143,6 +173,7 @@ public class RenameDialog extends JDialog { } private List updateDeobfMap(List deobfMap, String alias) { + LOG.trace("updateDeobfMap(): alias = " + alias); String id = alias.split("=")[0]; int i = 0; while (i < deobfMap.size()) { @@ -153,6 +184,7 @@ public class RenameDialog extends JDialog { i++; } } + LOG.trace("updateDeobfMap(): Placing alias = " + alias); deobfMap.add(alias); return deobfMap; } @@ -170,6 +202,7 @@ public class RenameDialog extends JDialog { return; } if (!refreshDeobfMapFile(renameText, root)) { + LOG.error("rename(): refreshDeobfMapFile() failed!"); dispose(); return; } @@ -190,7 +223,6 @@ public class RenameDialog extends JDialog { deobfMap = readDeobfMap(deobfMapPath); } catch (IOException e) { LOG.error("rename(): readDeobfMap() failed"); - dispose(); return false; } updateDeobfMap(deobfMap, getNodeAlias(renameText)); @@ -198,14 +230,6 @@ public class RenameDialog extends JDialog { writeDeobfMapFile(deobfMapPath, deobfMap); } catch (IOException e) { LOG.error("rename(): writeDeobfMap() failed"); - dispose(); - return false; - } - try { - writeDeobfMapFile(deobfMapPath, deobfMap); - } catch (IOException e) { - LOG.error("rename(): updateDeobfMap() failed"); - dispose(); return false; } return true; diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index df4db452c..0f1996034 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -138,6 +138,8 @@ msg.project_error_title=Fehler msg.project_error=Projekt konnte nicht geladen werden msg.rename_disabled_title=Umbenennen deaktiviert msg.rename_disabled=Einige der Umbenennungseinstellungen sind deaktiviert, bitte beachten Sie dies. +msg.rename_disabled_force_rewrite_enabled=Deaktivieren Sie zum Umbenennen die Option "Deobfuscationskartendatei umschreiben erzwingen". +msg.rename_disabled_deobfuscation_disabled=Bitte aktivieren Sie die Umbenennung von Deobfuscation. msg.cmd_select_class_error=Klasse\n%s auswählen nicht möglich\nSie existiert nicht. popup.undo=Rückgängig diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 636d06faa..9a4e7be22 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -138,6 +138,8 @@ msg.project_error_title=Error msg.project_error=Project could not be loaded msg.rename_disabled_title=Rename disabled msg.rename_disabled=Some of rename settings are disabled, please take this into consideration +msg.rename_disabled_force_rewrite_enabled=Please disable "Force rewrite deobfuscation map file" option to rename. +msg.rename_disabled_deobfuscation_disabled=Please enable deobfuscation to rename. msg.cmd_select_class_error=Failed to select the class\n%s\nThe class does not exist. popup.undo=Undo diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 727e2cbac..bbad9221b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -138,6 +138,8 @@ msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivar #msg.project_error= #msg.rename_disabled_title= #msg.rename_disabled= +#msg.rename_disabled_force_rewrite_enabled= +#msg.rename_disabled_deobfuscation_disabled= #msg.cmd_select_class_error= popup.undo=Deshacer diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 9e23bddc8..3c8df730b 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -138,6 +138,8 @@ msg.project_error_title=错误 msg.project_error=项目无法加载 msg.rename_disabled_title=重命名已禁用 msg.rename_disabled=某些重命名设置已禁用,请将此考虑在内 +msg.rename_disabled_force_rewrite_enabled=请禁用“强制覆盖反重构映射文件”选项以重命名。 +msg.rename_disabled_deobfuscation_disabled=请启用反混淆以重命名。 msg.cmd_select_class_error=无法选择类\n%s\n该类不存在。 popup.undo=撤销 From 8a8b945eb88cc13afa00df6dd35298d9c5530f52 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Fri, 3 Jul 2020 09:26:24 -0500 Subject: [PATCH 25/25] fix(gui): run indexJob before rename (PR #910) --- .../java/jadx/gui/ui/CommonSearchDialog.java | 4 +- .../main/java/jadx/gui/ui/RenameDialog.java | 48 +++++++++++-------- .../main/java/jadx/gui/ui/SearchDialog.java | 2 + .../main/java/jadx/gui/ui/UsageDialog.java | 3 +- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index 86073aa15..10fec4cbd 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -516,17 +516,15 @@ public abstract class CommonSearchDialog extends JDialog { } } - protected void loadStartCommon() { + private void loadStartCommon() { setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); progressPane.setIndeterminate(true); progressPane.setVisible(true); - resultsTable.setEnabled(false); warnLabel.setVisible(false); } private void loadFinishedCommon() { setCursor(null); - resultsTable.setEnabled(true); progressPane.setVisible(false); TextSearchIndex textIndex = cache.getTextIndex(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java index 5f3dd600d..b6a5e0eba 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -1,7 +1,6 @@ package jadx.gui.ui; import java.awt.*; -import java.awt.event.KeyEvent; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -42,7 +41,7 @@ import jadx.gui.ui.codearea.CodeArea; import jadx.gui.ui.codearea.CodePanel; import jadx.gui.utils.*; -public class RenameDialog extends JDialog { +public class RenameDialog extends CommonSearchDialog { private static final long serialVersionUID = -3269715644416902410L; private static final Logger LOG = LoggerFactory.getLogger(RenameDialog.class); @@ -55,6 +54,8 @@ public class RenameDialog extends JDialog { private CodeArea codeArea; + private JButton renameBtn; + public RenameDialog(CodeArea codeArea, JNode node) { super(codeArea.getMainWindow()); mainWindow = codeArea.getMainWindow(); @@ -62,6 +63,7 @@ public class RenameDialog extends JDialog { this.node = node; if (isDeobfuscationSettingsValid()) { initUI(); + registerInitOnOpen(); loadWindowPos(); } else { LOG.error("Deobfuscation settings are invalid - please enable deobfuscation and disable force rewrite deobfuscation map"); @@ -95,14 +97,19 @@ public class RenameDialog extends JDialog { JOptionPane.ERROR_MESSAGE); } - private void loadWindowPos() { - mainWindow.getSettings().loadWindowPos(this); + @Override + protected void openInit() { + prepare(); } @Override - public void dispose() { - mainWindow.getSettings().saveWindowPos(this); - super.dispose(); + protected void loadStart() { + renameBtn.setEnabled(false); + } + + @Override + protected void loadFinished() { + renameBtn.setEnabled(true); } private Path getDeobfMapPath(RootNode root) { @@ -239,7 +246,7 @@ public class RenameDialog extends JDialog { RenameVisitor renameVisitor = new RenameVisitor(); renameVisitor.init(rootNode); - mainWindow.getCacheObject().getNodeCache().refresh(node); + cache.getNodeCache().refresh(node); Set updatedClasses = getUpdatedClasses(); @@ -277,7 +284,7 @@ public class RenameDialog extends JDialog { private Set getUpdatedClasses() { Set usageClasses = new HashSet<>(); - CodeUsageInfo usageInfo = mainWindow.getCacheObject().getUsageInfo(); + CodeUsageInfo usageInfo = cache.getUsageInfo(); if (usageInfo != null) { usageInfo.getUsageList(node).forEach((node) -> { JavaClass rootClass = node.getRootClass().getCls(); @@ -290,7 +297,6 @@ public class RenameDialog extends JDialog { } private void setRefreshTask(Set refreshClasses) { - CacheObject cache = mainWindow.getCacheObject(); UnloadJob unloadJob = new UnloadJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses); RefreshJob refreshJob = new RefreshJob(mainWindow.getWrapper(), mainWindow.getSettings().getThreadsCount(), refreshClasses); LOG.info("Waiting for old unloadJob and refreshJob"); @@ -304,26 +310,24 @@ public class RenameDialog extends JDialog { LOG.info("Old unloadJob and refreshJob finished"); cache.setUnloadJob(unloadJob); cache.setRefreshJob(refreshJob); - cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), mainWindow.getCacheObject(), mainWindow.getSettings().getThreadsCount())); + cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), cache, mainWindow.getSettings().getThreadsCount())); mainWindow.runBackgroundUnloadRefreshAndIndexJobs(); } - private void initCommon() { - KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); - getRootPane().registerKeyboardAction(e -> dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); - } - @NotNull - private JPanel initButtonsPanel() { + protected JPanel initButtonsPanel() { JButton cancelButton = new JButton(NLS.str("search_dialog.cancel")); cancelButton.addActionListener(event -> dispose()); - JButton renameBtn = new JButton(NLS.str("popup.rename")); + renameBtn = new JButton(NLS.str("popup.rename")); renameBtn.addActionListener(event -> rename()); getRootPane().setDefaultButton(renameBtn); + progressPane = new ProgressPanel(mainWindow, false); + JPanel buttonPane = new JPanel(); buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS)); buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10)); + buttonPane.add(progressPane); buttonPane.add(Box.createRigidArea(new Dimension(5, 0))); buttonPane.add(Box.createHorizontalGlue()); buttonPane.add(renameBtn); @@ -349,8 +353,14 @@ public class RenameDialog extends JDialog { renamePane.add(nodeLabel); renamePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); + warnLabel = new JLabel(); + warnLabel.setForeground(Color.RED); + warnLabel.setVisible(false); + JPanel textPane = new JPanel(); - textPane.setLayout(new FlowLayout(FlowLayout.LEFT)); + textPane.setLayout(new BoxLayout(textPane, BoxLayout.PAGE_AXIS)); + textPane.add(warnLabel); + textPane.add(Box.createRigidArea(new Dimension(0, 5))); textPane.add(renameField); textPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java index 39fd2624f..50cdc1f25 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java @@ -282,11 +282,13 @@ public class SearchDialog extends CommonSearchDialog { @Override protected void loadFinished() { + resultsTable.setEnabled(true); searchField.setEnabled(true); } @Override protected void loadStart() { + resultsTable.setEnabled(false); searchField.setEnabled(false); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java index cbc0a6860..650d2f226 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/UsageDialog.java @@ -30,12 +30,13 @@ public class UsageDialog extends CommonSearchDialog { @Override protected void loadFinished() { + resultsTable.setEnabled(true); performSearch(); } @Override protected void loadStart() { - // no op + resultsTable.setEnabled(false); } @Override