Merge branch 'rename' into master
This commit is contained in:
@@ -454,5 +454,4 @@ public final class JadxDecompiler implements Closeable {
|
||||
public String toString() {
|
||||
return "jadx decompiler " + getVersion();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,11 @@ public final class JavaClass implements JavaNode {
|
||||
cls.decompile();
|
||||
}
|
||||
|
||||
public synchronized void refresh() {
|
||||
listsLoaded = false;
|
||||
cls.reRunDecompile();
|
||||
}
|
||||
|
||||
public synchronized String getSmali() {
|
||||
return cls.getSmali();
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
ClassAliasInfo newAlias = new ClassAliasInfo(getAliasPkg(), aliasName);
|
||||
fillAliasFullName(newAlias);
|
||||
this.alias = newAlias;
|
||||
} else {
|
||||
this.alias = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,12 @@ 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 java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -20,7 +23,7 @@ import jadx.core.dex.nodes.RootNode;
|
||||
public class ConstStorage {
|
||||
|
||||
private static final class ValueStorage {
|
||||
private final Map<Object, FieldNode> values = new HashMap<>();
|
||||
private final Map<Object, FieldNode> values = new ConcurrentHashMap<>();
|
||||
private final Set<Object> duplicates = new HashSet<>();
|
||||
|
||||
public Map<Object, FieldNode> getValues() {
|
||||
@@ -35,22 +38,33 @@ 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;
|
||||
}
|
||||
|
||||
public boolean contains(Object value) {
|
||||
return duplicates.contains(value) || values.containsKey(value);
|
||||
}
|
||||
|
||||
void removeForCls(ClassNode cls) {
|
||||
Iterator<Entry<Object, FieldNode>> it = values.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Entry<Object, FieldNode> entry = it.next();
|
||||
FieldNode field = entry.getValue();
|
||||
if (field.getParentClass().equals(cls)) {
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final boolean replaceEnabled;
|
||||
@@ -81,6 +95,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);
|
||||
|
||||
@@ -38,16 +38,19 @@ import jadx.core.dex.info.MethodInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
import jadx.core.dex.instructions.args.LiteralArg;
|
||||
import jadx.core.dex.nodes.parser.SignatureParser;
|
||||
import jadx.core.dex.visitors.ProcessAnonymous;
|
||||
import jadx.core.utils.SmaliUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
import static jadx.core.dex.nodes.ProcessState.LOADED;
|
||||
import static jadx.core.dex.nodes.ProcessState.NOT_LOADED;
|
||||
|
||||
public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeNode, Comparable<ClassNode> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
|
||||
|
||||
private final RootNode root;
|
||||
private final IClassData cls;
|
||||
private final int clsDefOffset;
|
||||
@Nullable
|
||||
private final Path inputPath;
|
||||
@@ -58,8 +61,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
private List<ArgType> interfaces;
|
||||
private List<ArgType> generics = Collections.emptyList();
|
||||
|
||||
private final List<MethodNode> methods;
|
||||
private final List<FieldNode> fields;
|
||||
private List<MethodNode> methods;
|
||||
private List<FieldNode> fields;
|
||||
private List<ClassNode> innerClasses = Collections.emptyList();
|
||||
|
||||
private List<ClassNode> inlinedClasses = Collections.emptyList();
|
||||
@@ -86,6 +89,11 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
this.inputPath = cls.getInputPath();
|
||||
this.clsDefOffset = cls.getClassDefOffset();
|
||||
this.clsInfo = ClassInfo.fromType(root, ArgType.object(cls.getType()));
|
||||
initialLoad(cls);
|
||||
this.cls = cls.copy(); // TODO: need only for rename feature
|
||||
}
|
||||
|
||||
private void initialLoad(IClassData cls) {
|
||||
try {
|
||||
String superType = cls.getSuperType();
|
||||
if (superType == null) {
|
||||
@@ -144,6 +152,7 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
// Create empty class
|
||||
private ClassNode(RootNode root, String name, int accessFlags) {
|
||||
this.root = root;
|
||||
this.cls = null;
|
||||
this.inputPath = null;
|
||||
this.clsDefOffset = 0;
|
||||
this.clsInfo = ClassInfo.fromName(root, name);
|
||||
@@ -279,10 +288,31 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
return decompile(true);
|
||||
}
|
||||
|
||||
public ICodeInfo reloadCode() {
|
||||
public synchronized ICodeInfo reRunDecompile() {
|
||||
return decompile(false);
|
||||
}
|
||||
|
||||
public synchronized ICodeInfo reloadCode() {
|
||||
unload();
|
||||
deepUnload();
|
||||
return decompile(false);
|
||||
}
|
||||
|
||||
public void deepUnload() {
|
||||
if (cls == null) {
|
||||
// manually added class
|
||||
return;
|
||||
}
|
||||
clearAttributes();
|
||||
root().getConstValues().removeForClass(this);
|
||||
initialLoad(cls);
|
||||
ProcessAnonymous.runForClass(this);
|
||||
|
||||
for (ClassNode innerClass : innerClasses) {
|
||||
innerClass.deepUnload();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized ICodeInfo decompile(boolean searchInCache) {
|
||||
ICodeCache codeCache = root().getCodeCache();
|
||||
ClassNode topParentClass = getTopParentClass();
|
||||
@@ -319,6 +349,8 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN
|
||||
innerClasses.forEach(ClassNode::unload);
|
||||
fields.forEach(FieldNode::unloadAttributes);
|
||||
unloadAttributes();
|
||||
setState(NOT_LOADED);
|
||||
this.smali = null;
|
||||
}
|
||||
|
||||
private void buildCache() {
|
||||
|
||||
@@ -172,6 +172,15 @@ public class MethodNode extends NotificationAttrNode implements IMethodDetails,
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
unload();
|
||||
try {
|
||||
load();
|
||||
} catch (DecodeException e) {
|
||||
throw new JadxRuntimeException("Failed to reload method " + getClass().getName() + "." + getName());
|
||||
}
|
||||
}
|
||||
|
||||
public void initMethodTypes() {
|
||||
if (!parseSignature()) {
|
||||
this.retType = mthInfo.getReturnType();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,6 +61,11 @@ public class JadxCodeAssertions extends AbstractStringAssert<JadxCodeAssertions>
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
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())
|
||||
.removeBlockComments() // remove comment about inlined class
|
||||
.print()
|
||||
.containsOnlyOnce("return new Runnable() {")
|
||||
.doesNotContain("AnonymousClass1");
|
||||
}
|
||||
}
|
||||
@@ -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;");
|
||||
}
|
||||
}
|
||||
@@ -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 {");
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,24 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
|
||||
System.gc();
|
||||
LOG.debug("Memory usage: Before decompile: {}", UiUtils.memoryInfo());
|
||||
runJob(cache.getDecompileJob());
|
||||
LOG.debug("Memory usage: After decompile: {}", UiUtils.memoryInfo());
|
||||
|
||||
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());
|
||||
@@ -74,7 +92,7 @@ public class BackgroundWorker extends SwingWorker<Void, Void> {
|
||||
}
|
||||
|
||||
private void runJob(BackgroundJob job) {
|
||||
if (isCancelled()) {
|
||||
if (isCancelled() || job == null) {
|
||||
return;
|
||||
}
|
||||
progressPane.changeLabel(this, job.getInfoString());
|
||||
|
||||
@@ -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<JavaClass> refreshClasses;
|
||||
|
||||
public RefreshJob(JadxWrapper jadxWrapper, int threadsCount, Set<JavaClass> refreshClasses) {
|
||||
super(jadxWrapper, threadsCount);
|
||||
this.refreshClasses = refreshClasses;
|
||||
}
|
||||
|
||||
protected void runJob() {
|
||||
for (final JavaClass cls : refreshClasses) {
|
||||
addTask(cls::refresh);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getInfoString() {
|
||||
return "Refreshing: ";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<JavaClass> refreshClasses;
|
||||
|
||||
public UnloadJob(JadxWrapper jadxWrapper, int threadsCount, Set<JavaClass> 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: ";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -397,7 +397,12 @@ public class MainWindow extends JFrame {
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
}
|
||||
|
||||
private synchronized void runBackgroundJobs() {
|
||||
public void resetIndex() {
|
||||
int threadsCount = settings.getThreadsCount();
|
||||
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
|
||||
}
|
||||
|
||||
synchronized void runBackgroundJobs() {
|
||||
cancelBackgroundJobs();
|
||||
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
|
||||
if (settings.isAutoStartJobs()) {
|
||||
@@ -410,6 +415,12 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
}
|
||||
|
||||
synchronized void runBackgroundUnloadRefreshAndIndexJobs() {
|
||||
cancelBackgroundJobs();
|
||||
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
|
||||
backgroundWorker.exec();
|
||||
}
|
||||
|
||||
public synchronized void cancelBackgroundJobs() {
|
||||
backgroundExecutor.cancelAll();
|
||||
if (backgroundWorker != null) {
|
||||
@@ -513,7 +524,7 @@ public class MainWindow extends JFrame {
|
||||
treeModel.reload();
|
||||
}
|
||||
|
||||
private void reloadTree() {
|
||||
public void reloadTree() {
|
||||
treeReloading = true;
|
||||
|
||||
treeModel.reload();
|
||||
|
||||
@@ -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;
|
||||
@@ -9,7 +8,10 @@ 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;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
@@ -22,16 +24,22 @@ import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.core.dex.nodes.RootNode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
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;
|
||||
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.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
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);
|
||||
@@ -44,23 +52,62 @@ public class RenameDialog extends JDialog {
|
||||
|
||||
private CodeArea codeArea;
|
||||
|
||||
private JButton renameBtn;
|
||||
|
||||
public RenameDialog(CodeArea codeArea, JNode node) {
|
||||
super(codeArea.getMainWindow());
|
||||
mainWindow = codeArea.getMainWindow();
|
||||
this.codeArea = codeArea;
|
||||
this.node = node;
|
||||
initUI();
|
||||
loadWindowPos();
|
||||
if (isDeobfuscationSettingsValid()) {
|
||||
initUI();
|
||||
registerInitOnOpen();
|
||||
loadWindowPos();
|
||||
} else {
|
||||
LOG.error("Deobfuscation settings are invalid - please enable deobfuscation and disable force rewrite deobfuscation map");
|
||||
}
|
||||
}
|
||||
|
||||
private void loadWindowPos() {
|
||||
mainWindow.getSettings().loadWindowPos(this);
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
mainWindow.getSettings().saveWindowPos(this);
|
||||
super.dispose();
|
||||
protected void openInit() {
|
||||
prepare();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadStart() {
|
||||
renameBtn.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadFinished() {
|
||||
renameBtn.setEnabled(true);
|
||||
}
|
||||
|
||||
private Path getDeobfMapPath(RootNode root) {
|
||||
@@ -103,52 +150,52 @@ 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<String> 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<String> 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);
|
||||
LOG.trace("Copying " + tmpFile.toPath() + " to " + deobfMapPath);
|
||||
Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
Files.delete(oldMap.toPath());
|
||||
Files.delete(tmpFile.toPath());
|
||||
return true;
|
||||
}
|
||||
|
||||
private List<String> readAndUpdateDeobfMap(Path deobfMapPath, String alias) throws IOException {
|
||||
List<String> deobfMap = Files.readAllLines(deobfMapPath, StandardCharsets.UTF_8);
|
||||
@NotNull
|
||||
private List<String> readDeobfMap(Path deobfMapPath) throws IOException {
|
||||
return Files.readAllLines(deobfMapPath, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private List<String> updateDeobfMap(List<String> deobfMap, String alias) {
|
||||
LOG.trace("updateDeobfMap(): alias = " + 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++;
|
||||
}
|
||||
}
|
||||
LOG.trace("updateDeobfMap(): Placing alias = " + alias);
|
||||
deobfMap.add(alias);
|
||||
return deobfMap;
|
||||
}
|
||||
|
||||
private void rename() {
|
||||
long start = System.nanoTime();
|
||||
String renameText = renameField.getText();
|
||||
if (renameText == null || renameText.length() == 0 || codeArea.getText() == null) {
|
||||
return;
|
||||
@@ -159,31 +206,126 @@ public class RenameDialog extends JDialog {
|
||||
dispose();
|
||||
return;
|
||||
}
|
||||
if (!updateDeobfMap(renameText, root)) {
|
||||
LOG.error("rename(): updateDeobfMap() failed");
|
||||
if (!refreshDeobfMapFile(renameText, root)) {
|
||||
LOG.error("rename(): refreshDeobfMapFile() failed!");
|
||||
dispose();
|
||||
return;
|
||||
}
|
||||
mainWindow.reOpenFile();
|
||||
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();
|
||||
}
|
||||
|
||||
private void initCommon() {
|
||||
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
|
||||
getRootPane().registerKeyboardAction(e -> dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
|
||||
private boolean refreshDeobfMapFile(String renameText, RootNode root) {
|
||||
List<String> deobfMap;
|
||||
Path deobfMapPath = getDeobfMapPath(root);
|
||||
try {
|
||||
deobfMap = readDeobfMap(deobfMapPath);
|
||||
} catch (IOException e) {
|
||||
LOG.error("rename(): readDeobfMap() failed");
|
||||
return false;
|
||||
}
|
||||
updateDeobfMap(deobfMap, getNodeAlias(renameText));
|
||||
try {
|
||||
writeDeobfMapFile(deobfMapPath, deobfMap);
|
||||
} catch (IOException e) {
|
||||
LOG.error("rename(): writeDeobfMap() failed");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int refreshState(RootNode rootNode) {
|
||||
RenameVisitor renameVisitor = new RenameVisitor();
|
||||
renameVisitor.init(rootNode);
|
||||
|
||||
cache.getNodeCache().refresh(node);
|
||||
|
||||
Set<JavaClass> updatedClasses = getUpdatedClasses();
|
||||
|
||||
mainWindow.reloadTree();
|
||||
refreshTabs(mainWindow.getTabbedPane(), updatedClasses);
|
||||
|
||||
if (updatedClasses.size() > 0) {
|
||||
setRefreshTask(updatedClasses);
|
||||
}
|
||||
|
||||
return updatedClasses.size();
|
||||
}
|
||||
|
||||
private void refreshTabs(TabbedPane tabbedPane, Set<JavaClass> updatedClasses) {
|
||||
for (Map.Entry<JNode, ContentPanel> panel : tabbedPane.getOpenTabs().entrySet()) {
|
||||
ContentPanel contentPanel = panel.getValue();
|
||||
if (contentPanel instanceof ClassCodeContentPanel) {
|
||||
JNode node = panel.getKey();
|
||||
JClass rootClass = node.getRootClass();
|
||||
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();
|
||||
javaPanel.refresh();
|
||||
tabbedPane.refresh(node);
|
||||
updatedClasses.remove(javaClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<JavaClass> getUpdatedClasses() {
|
||||
Set<JavaClass> usageClasses = new HashSet<>();
|
||||
CodeUsageInfo usageInfo = cache.getUsageInfo();
|
||||
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;
|
||||
}
|
||||
|
||||
private void setRefreshTask(Set<JavaClass> refreshClasses) {
|
||||
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");
|
||||
while (cache.getUnloadJob() != null || cache.getRefreshJob() != null) {
|
||||
try {
|
||||
Thread.sleep(10);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
LOG.info("Old unloadJob and refreshJob finished");
|
||||
cache.setUnloadJob(unloadJob);
|
||||
cache.setRefreshJob(refreshJob);
|
||||
cache.setIndexJob(new IndexJob(mainWindow.getWrapper(), cache, mainWindow.getSettings().getThreadsCount()));
|
||||
mainWindow.runBackgroundUnloadRefreshAndIndexJobs();
|
||||
}
|
||||
|
||||
@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);
|
||||
@@ -209,8 +351,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));
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -70,4 +70,8 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel {
|
||||
return javaCodePanel.getCodeArea();
|
||||
}
|
||||
|
||||
public CodePanel getJavaCodePanel() {
|
||||
return javaCodePanel;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,6 +67,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);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,6 +7,8 @@ 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;
|
||||
|
||||
@@ -14,6 +16,8 @@ public class CacheObject {
|
||||
|
||||
private DecompileJob decompileJob;
|
||||
private IndexJob indexJob;
|
||||
private UnloadJob unloadJob;
|
||||
private RefreshJob refreshJob;
|
||||
|
||||
private TextSearchIndex textIndex;
|
||||
private CodeUsageInfo usageInfo;
|
||||
@@ -89,4 +93,20 @@ public class CacheObject {
|
||||
public Set<SearchDialog.SearchOptions> getLastSearchOptions() {
|
||||
return lastSearchOptions;
|
||||
}
|
||||
|
||||
public RefreshJob getRefreshJob() {
|
||||
return refreshJob;
|
||||
}
|
||||
|
||||
public void setRefreshJob(RefreshJob refreshJob) {
|
||||
this.refreshJob = refreshJob;
|
||||
}
|
||||
|
||||
public UnloadJob getUnloadJob() {
|
||||
return unloadJob;
|
||||
}
|
||||
|
||||
public void setUnloadJob(UnloadJob unloadJob) {
|
||||
this.unloadJob = unloadJob;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=撤销
|
||||
|
||||
+5
@@ -27,6 +27,11 @@ public class DexClassData implements IClassData {
|
||||
this.annotationsParser = annotationsParser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IClassData copy() {
|
||||
return new DexClassData(in.copy(), annotationsParser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
int typeIdx = in.pos(0).readInt();
|
||||
|
||||
+1
@@ -10,6 +10,7 @@ import jadx.api.plugins.input.data.annotations.EncodedValue;
|
||||
import jadx.api.plugins.input.data.annotations.IAnnotation;
|
||||
|
||||
public interface IClassData {
|
||||
IClassData copy();
|
||||
|
||||
String getType();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user