diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index c469554e8064f419d59314c513218c36f2e55d56..40ad7b9f60ca1f187ccac1e8d5c58ca3db6a0d97 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -57,7 +57,7 @@ public final class JavaClass implements JavaNode { cls.decompile(); } - public synchronized void refresh() { + public synchronized void reload() { listsLoaded = false; cls.reloadCode(); } diff --git a/jadx-core/src/main/java/jadx/core/ProcessClass.java b/jadx-core/src/main/java/jadx/core/ProcessClass.java index 131703178b8de7b2115fc476945cae10e6afbd80..2457f98bcdf1cb80e924ac58193cc2de9faa0cb4 100644 --- a/jadx-core/src/main/java/jadx/core/ProcessClass.java +++ b/jadx-core/src/main/java/jadx/core/ProcessClass.java @@ -31,6 +31,12 @@ public final class ProcessClass { } synchronized (cls.getClassInfo()) { try { + if (cls.contains(AFlag.CLASS_DEEP_RELOAD)) { + cls.remove(AFlag.CLASS_DEEP_RELOAD); + cls.unload(); + cls.deepUnload(); + cls.root().runPreDecompileStageForClass(cls); + } if (codegen) { if (cls.getState() == GENERATED_AND_UNLOADED) { // allow to run code generation again diff --git a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java index 374dc1a6401064d4ad79876a449198835b759890..5f4bcf063756885795973ed5270a7ed2fd7a7412 100644 --- a/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java +++ b/jadx-core/src/main/java/jadx/core/dex/attributes/AFlag.java @@ -22,9 +22,6 @@ public enum AFlag { HIDDEN, // instruction used inside other instruction but not listed in args - RESTART_CODEGEN, // codegen must be executed again - RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage - DONT_RENAME, // do not rename during deobfuscation ADDED_TO_REGION, @@ -76,5 +73,10 @@ public enum AFlag { REQUEST_IF_REGION_OPTIMIZE, // run if region visitor again + // Class processing flags + RESTART_CODEGEN, // codegen must be executed again + RELOAD_AT_CODEGEN_STAGE, // class can't be analyzed at 'process' stage => unload before 'codegen' stage + CLASS_DEEP_RELOAD, // perform deep class unload (reload) before process + DONT_UNLOAD_CLASS, // don't unload class after code generation (only for tests and debug!) } 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 8ae37f1f05073a3fc15d13ba4f916ef16bc3cc15..cc5ce0b291dac1c30fbf6397490d4faabec92ebc 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 @@ -225,10 +225,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN return decompile(true); } - public synchronized ICodeInfo reloadCode() { - unload(); - deepUnload(); - root.runPreDecompileStageForClass(this); + public ICodeInfo reloadCode() { + add(AFlag.CLASS_DEEP_RELOAD); return decompile(false); } diff --git a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java index 5739b137b80c50ae097a70b4bfde63158b642eca..ffe693e6c69b7ef0b5d76def42960167a2fdff41 100644 --- a/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java +++ b/jadx-core/src/main/java/jadx/core/dex/visitors/OverrideMethodVisitor.java @@ -8,6 +8,7 @@ import java.util.Objects; import jadx.core.clsp.ClspClass; import jadx.core.clsp.ClspMethod; +import jadx.core.dex.attributes.AType; import jadx.core.dex.attributes.nodes.MethodOverrideAttr; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.instructions.args.ArgType; @@ -33,20 +34,25 @@ public class OverrideMethodVisitor extends AbstractVisitor { public boolean visit(ClassNode cls) throws JadxException { List superTypes = collectSuperTypes(cls); for (MethodNode mth : cls.getMethods()) { - if (mth.isConstructor() || mth.getAccessFlags().isStatic()) { - continue; - } - String signature = mth.getMethodInfo().makeSignature(false); - List overrideList = collectOverrideMethods(cls, superTypes, signature); - if (!overrideList.isEmpty()) { - mth.addAttr(new MethodOverrideAttr(overrideList)); - fixMethodReturnType(mth, overrideList, superTypes); - fixMethodArgTypes(mth, overrideList, superTypes); - } + processMth(cls, superTypes, mth); } return true; } + private void processMth(ClassNode cls, List superTypes, MethodNode mth) { + if (mth.isConstructor() || mth.getAccessFlags().isStatic()) { + return; + } + mth.remove(AType.METHOD_OVERRIDE); + String signature = mth.getMethodInfo().makeSignature(false); + List overrideList = collectOverrideMethods(cls, superTypes, signature); + if (!overrideList.isEmpty()) { + mth.addAttr(new MethodOverrideAttr(overrideList)); + fixMethodReturnType(mth, overrideList, superTypes); + fixMethodArgTypes(mth, overrideList, superTypes); + } + } + private List collectOverrideMethods(ClassNode cls, List superTypes, String signature) { List overrideList = new ArrayList<>(); for (ArgType superType : superTypes) { 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 8f7aada3c310a7be2b49b4a26d9ccf76ca8e9be5..f6131dc7d75a58664f702f470555d2722b638d61 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -59,8 +59,8 @@ public class JClass extends JLoadableNode { update(); } - public synchronized void refresh() { - cls.refresh(); + public synchronized void reload() { + cls.reload(); loaded = true; update(); cls.unload(); diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java index ef01cc91434c8a78a14d7ecd84118a4ed654436d..ccbd855d5193f32061479ae20a1668f0d415dcfd 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java @@ -1,14 +1,15 @@ package jadx.gui.treemodel; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.swing.*; import org.jetbrains.annotations.NotNull; -import jadx.api.JavaClass; import jadx.api.JavaPackage; +import jadx.core.utils.Utils; import jadx.gui.JadxWrapper; import jadx.gui.utils.UiUtils; @@ -17,42 +18,41 @@ public class JPackage extends JNode implements Comparable { private static final ImageIcon PACKAGE_ICON = UiUtils.openIcon("package_obj"); - private final String fullName; + private String fullName; private String name; private boolean enabled; - private final List classes; - private final List innerPackages = new ArrayList<>(); + private List classes; + private List innerPackages; public JPackage(JavaPackage pkg, JadxWrapper wrapper) { - this.fullName = pkg.getName(); - this.name = pkg.getName(); - setEnabled(wrapper); - List javaClasses = pkg.getClasses(); - this.classes = new ArrayList<>(javaClasses.size()); - for (JavaClass javaClass : javaClasses) { - classes.add(new JClass(javaClass)); - } + this(pkg.getName(), pkg.getName(), + isPkgEnabled(wrapper, pkg.getName()), + Utils.collectionMap(pkg.getClasses(), JClass::new), + new ArrayList<>()); update(); } - public JPackage(String name, JadxWrapper wrapper) { - this.fullName = name; - this.name = name; - setEnabled(wrapper); - this.classes = new ArrayList<>(); + public JPackage(String fullName, JadxWrapper wrapper) { + this(fullName, fullName, isPkgEnabled(wrapper, fullName), new ArrayList<>(), new ArrayList<>()); } public JPackage(String fullName, String name) { + this(fullName, name, true, Collections.emptyList(), Collections.emptyList()); + } + + private JPackage(String fullName, String name, boolean enabled, List classes, List innerPackages) { this.fullName = fullName; this.name = name; - this.classes = new ArrayList<>(); + this.enabled = enabled; + this.classes = classes; + this.innerPackages = innerPackages; } - private void setEnabled(JadxWrapper wrapper) { + private static boolean isPkgEnabled(JadxWrapper wrapper, String fullPkgName) { List excludedPackages = wrapper.getExcludedPackages(); - this.enabled = excludedPackages.isEmpty() + return excludedPackages.isEmpty() || excludedPackages.stream().filter(p -> !p.isEmpty()) - .noneMatch(p -> name.equals(p) || name.startsWith(p + '.')); + .noneMatch(p -> fullPkgName.equals(p) || fullPkgName.startsWith(p + '.')); } public final void update() { @@ -78,7 +78,13 @@ public class JPackage extends JNode implements Comparable { return fullName; } - public void setName(String name) { + public void updateBothNames(String fullName, String name, JadxWrapper wrapper) { + this.fullName = fullName; + this.name = name; + this.enabled = isPkgEnabled(wrapper, fullName); + } + + public void updateName(String name) { this.name = name; } @@ -86,10 +92,18 @@ public class JPackage extends JNode implements Comparable { return innerPackages; } + public void setInnerPackages(List innerPackages) { + this.innerPackages = innerPackages; + } + public List getClasses() { return classes; } + public void setClasses(List classes) { + this.classes = classes; + } + @Override public Icon getIcon() { return PACKAGE_ICON; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java index 5d69ba151cf7ce0dcf96dd8541888f198e45a7de..bd50db22a72914bfe62ed5a6b0a1a86a05a02005 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -62,16 +61,16 @@ public class JSources extends JNode { do { repeat = false; for (JPackage pkg : pkgMap.values()) { - if (pkg.getInnerPackages().size() == 1 && pkg.getClasses().isEmpty()) { - JPackage innerPkg = pkg.getInnerPackages().get(0); - pkg.getInnerPackages().clear(); - pkg.getInnerPackages().addAll(innerPkg.getInnerPackages()); - pkg.getClasses().addAll(innerPkg.getClasses()); - pkg.setName(pkg.getName() + '.' + innerPkg.getName()); - - innerPkg.getInnerPackages().clear(); - innerPkg.getClasses().clear(); - + List innerPackages = pkg.getInnerPackages(); + if (innerPackages.size() == 1 && pkg.getClasses().isEmpty()) { + JPackage innerPkg = innerPackages.get(0); + pkg.setInnerPackages(innerPkg.getInnerPackages()); + pkg.setClasses(innerPkg.getClasses()); + String innerName = '.' + innerPkg.getName(); + pkg.updateBothNames(pkg.getFullName() + innerName, pkg.getName() + innerName, wrapper); + + innerPkg.setInnerPackages(Collections.emptyList()); + innerPkg.setClasses(Collections.emptyList()); repeat = true; break; } @@ -79,12 +78,8 @@ public class JSources extends JNode { } while (repeat); // remove empty packages - for (Iterator> it = pkgMap.entrySet().iterator(); it.hasNext();) { - JPackage pkg = it.next().getValue(); - if (pkg.getInnerPackages().isEmpty() && pkg.getClasses().isEmpty()) { - it.remove(); - } - } + pkgMap.values().removeIf(pkg -> pkg.getInnerPackages().isEmpty() && pkg.getClasses().isEmpty()); + // use identity set for collect inner packages Set innerPackages = Collections.newSetFromMap(new IdentityHashMap<>()); for (JPackage pkg : pkgMap.values()) { @@ -102,7 +97,7 @@ public class JSources extends JNode { } private void addPackage(Map pkgs, JPackage pkg) { - String pkgName = pkg.getName(); + String pkgName = pkg.getFullName(); JPackage replaced = pkgs.put(pkgName, pkg); if (replaced != null) { pkg.getInnerPackages().addAll(replaced.getInnerPackages()); @@ -112,7 +107,7 @@ public class JSources extends JNode { if (dot > 0) { String prevPart = pkgName.substring(0, dot); String shortName = pkgName.substring(dot + 1); - pkg.setName(shortName); + pkg.updateName(shortName); JPackage prevPkg = pkgs.get(prevPart); if (prevPkg == null) { prevPkg = new JPackage(prevPart, wrapper); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java b/jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java index a1dc456df2807cc9bfd6c5a613940eebf3364705..3670d4c4c3456da1e50a790aac97a2ae16e48b03 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/JPackagePopupMenu.java @@ -3,12 +3,11 @@ package jadx.gui.ui; import java.util.Arrays; import java.util.List; -import javax.swing.JCheckBoxMenuItem; -import javax.swing.JMenu; -import javax.swing.JMenuItem; -import javax.swing.JPopupMenu; +import javax.swing.*; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import jadx.gui.JadxWrapper; import jadx.gui.treemodel.JClass; @@ -18,6 +17,8 @@ import jadx.gui.utils.NLS; class JPackagePopupMenu extends JPopupMenu { private static final long serialVersionUID = -7781009781149224131L; + private static final Logger LOG = LoggerFactory.getLogger(JPackagePopupMenu.class); + private final transient MainWindow mainWindow; public JPackagePopupMenu(MainWindow mainWindow, JPackage pkg) { @@ -32,8 +33,8 @@ class JPackagePopupMenu extends JPopupMenu { @Nullable private JMenuItem makeRenameMenuItem(JPackage pkg) { - List aliasParts = splitPackage(pkg.getName()); - int count = aliasParts.size(); + List aliasShortParts = splitPackage(pkg.getName()); + int count = aliasShortParts.size(); if (count == 0) { return null; } @@ -41,20 +42,20 @@ class JPackagePopupMenu extends JPopupMenu { if (rawPackage == null) { return null; } + List aliasParts = splitPackage(pkg.getFullName()); + List rawParts = splitPackage(rawPackage); // can be longer then alias parts + int start = aliasParts.size() - count; if (count == 1) { // single case => no submenu - String aliasPkg = aliasParts.get(0); - JPackage renamePkg = new JPackage(rawPackage, aliasPkg); + JPackage renamePkg = new JPackage(concat(rawParts, start), aliasParts.get(start)); JMenuItem pkgItem = new JMenuItem(NLS.str("popup.rename")); pkgItem.addActionListener(e -> rename(renamePkg)); return pkgItem; } - List rawParts = splitPackage(rawPackage); // can be longer then alias JMenuItem renameSubMenu = new JMenu(NLS.str("popup.rename")); - for (int i = 0; i < count; i++) { - String rawPkg = concat(rawParts, i); + for (int i = start; i < aliasParts.size(); i++) { String aliasShortPkg = aliasParts.get(i); - JPackage pkgPart = new JPackage(rawPkg, aliasShortPkg); + JPackage pkgPart = new JPackage(concat(rawParts, i), aliasShortPkg); JMenuItem pkgPartItem = new JMenuItem(aliasShortPkg); pkgPartItem.addActionListener(e -> rename(pkgPart)); renameSubMenu.add(pkgPartItem); @@ -76,8 +77,9 @@ class JPackagePopupMenu extends JPopupMenu { return sb.toString(); } - private void rename(JPackage pkgPart) { - new RenameDialog(mainWindow, pkgPart).setVisible(true); + private void rename(JPackage pkg) { + LOG.debug("Renaming package: fullName={}, name={}", pkg.getFullName(), pkg.getName()); + new RenameDialog(mainWindow, pkg).setVisible(true); } private List splitPackage(String rawPackage) { 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 116f5eaad8870826aa72a74d908dc4608d0198a9..f534509a294c72606749137afdd2d7d220574461 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/RenameDialog.java @@ -269,8 +269,12 @@ public class RenameDialog extends JDialog { } private void refreshJClass(JClass cls) { - cls.refresh(); - IndexJob.refreshIndex(cache, cls.getCls()); + try { + cls.reload(); + IndexJob.refreshIndex(cache, cls.getCls()); + } catch (Throwable e) { + LOG.error("Failed to reload class: {}", cls, e); + } } private void refreshTabs(TabbedPane tabbedPane, Set updatedClasses) { diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java index 8360457b17989cb1446116311dc10c857abbcffa..c02a7f5e0951f049386d7f0caf4ac9f91f48f09b 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/DexClassData.java @@ -29,7 +29,7 @@ public class DexClassData implements IClassData { @Override public IClassData copy() { - return new DexClassData(in.copy(), annotationsParser); + return new DexClassData(in.copy(), annotationsParser.copy()); } @Override diff --git a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java index cf8a8ba6f2e6064f83bb588cf739183dfc472cbb..d723e2c0e3eaa3ecfb14a0f5b82854ebd95e4212 100644 --- a/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java +++ b/jadx-plugins/jadx-dex-input/src/main/java/jadx/plugins/input/dex/sections/annotations/AnnotationsParser.java @@ -27,6 +27,10 @@ public class AnnotationsParser { this.ext = ext; } + public AnnotationsParser copy() { + return new AnnotationsParser(in.copy(), ext.copy()); + } + public void setOffset(int offset) { this.offset = offset; if (offset == 0) {