Merge branch 'rename' into master

This commit is contained in:
Skylot
2020-08-02 13:25:15 +03:00
34 changed files with 571 additions and 71 deletions
@@ -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=撤销
@@ -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();
@@ -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();