fix: resolve several issues with package rename and class reload (#987)
This commit is contained in:
@@ -57,7 +57,7 @@ public final class JavaClass implements JavaNode {
|
||||
cls.decompile();
|
||||
}
|
||||
|
||||
public synchronized void refresh() {
|
||||
public synchronized void reload() {
|
||||
listsLoaded = false;
|
||||
cls.reloadCode();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!)
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ArgType> superTypes = collectSuperTypes(cls);
|
||||
for (MethodNode mth : cls.getMethods()) {
|
||||
if (mth.isConstructor() || mth.getAccessFlags().isStatic()) {
|
||||
continue;
|
||||
}
|
||||
String signature = mth.getMethodInfo().makeSignature(false);
|
||||
List<IMethodDetails> 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<ArgType> superTypes, MethodNode mth) {
|
||||
if (mth.isConstructor() || mth.getAccessFlags().isStatic()) {
|
||||
return;
|
||||
}
|
||||
mth.remove(AType.METHOD_OVERRIDE);
|
||||
String signature = mth.getMethodInfo().makeSignature(false);
|
||||
List<IMethodDetails> overrideList = collectOverrideMethods(cls, superTypes, signature);
|
||||
if (!overrideList.isEmpty()) {
|
||||
mth.addAttr(new MethodOverrideAttr(overrideList));
|
||||
fixMethodReturnType(mth, overrideList, superTypes);
|
||||
fixMethodArgTypes(mth, overrideList, superTypes);
|
||||
}
|
||||
}
|
||||
|
||||
private List<IMethodDetails> collectOverrideMethods(ClassNode cls, List<ArgType> superTypes, String signature) {
|
||||
List<IMethodDetails> overrideList = new ArrayList<>();
|
||||
for (ArgType superType : superTypes) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<JPackage> {
|
||||
|
||||
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<JClass> classes;
|
||||
private final List<JPackage> innerPackages = new ArrayList<>();
|
||||
private List<JClass> classes;
|
||||
private List<JPackage> innerPackages;
|
||||
|
||||
public JPackage(JavaPackage pkg, JadxWrapper wrapper) {
|
||||
this.fullName = pkg.getName();
|
||||
this.name = pkg.getName();
|
||||
setEnabled(wrapper);
|
||||
List<JavaClass> 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 = fullName;
|
||||
this.name = name;
|
||||
this.classes = new ArrayList<>();
|
||||
this(fullName, name, true, Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
private void setEnabled(JadxWrapper wrapper) {
|
||||
private JPackage(String fullName, String name, boolean enabled, List<JClass> classes, List<JPackage> innerPackages) {
|
||||
this.fullName = fullName;
|
||||
this.name = name;
|
||||
this.enabled = enabled;
|
||||
this.classes = classes;
|
||||
this.innerPackages = innerPackages;
|
||||
}
|
||||
|
||||
private static boolean isPkgEnabled(JadxWrapper wrapper, String fullPkgName) {
|
||||
List<String> 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<JPackage> {
|
||||
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<JPackage> {
|
||||
return innerPackages;
|
||||
}
|
||||
|
||||
public void setInnerPackages(List<JPackage> innerPackages) {
|
||||
this.innerPackages = innerPackages;
|
||||
}
|
||||
|
||||
public List<JClass> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public void setClasses(List<JClass> classes) {
|
||||
this.classes = classes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return PACKAGE_ICON;
|
||||
|
||||
@@ -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<JPackage> 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<Map.Entry<String, JPackage>> 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<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
@@ -102,7 +97,7 @@ public class JSources extends JNode {
|
||||
}
|
||||
|
||||
private void addPackage(Map<String, JPackage> 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);
|
||||
|
||||
@@ -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<String> aliasParts = splitPackage(pkg.getName());
|
||||
int count = aliasParts.size();
|
||||
List<String> 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<String> aliasParts = splitPackage(pkg.getFullName());
|
||||
List<String> 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<String> 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<String> splitPackage(String rawPackage) {
|
||||
|
||||
@@ -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<JClass> updatedClasses) {
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+4
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user