Skip to content

Commit

Permalink
feat(script): add methods for apply renames and reload tabs (#2008)
Browse files Browse the repository at this point in the history
  • Loading branch information
skylot committed Sep 15, 2023
1 parent b78a025 commit c39a696
Show file tree
Hide file tree
Showing 23 changed files with 351 additions and 144 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ public class NodeRenamedByUser implements IJadxEvent {
private final String oldName;
private final String newName;

/**
* Optional JRenameNode instance
*/
private Object renameNode;

public NodeRenamedByUser(ICodeNodeRef node, String oldName, String newName) {
this.node = node;
this.oldName = oldName;
Expand All @@ -29,6 +34,14 @@ public String getNewName() {
return newName;
}

public Object getRenameNode() {
return renameNode;
}

public void setRenameNode(Object renameNode) {
this.renameNode = renameNode;
}

@Override
public JadxEventType<NodeRenamedByUser> getType() {
return JadxEvents.NODE_RENAMED_BY_USER;
Expand Down
15 changes: 15 additions & 0 deletions jadx-core/src/main/java/jadx/api/plugins/gui/JadxGuiContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,19 @@ void addPopupMenuAction(String name,
* @return if successfully jumped to the code ref
*/
boolean open(ICodeNodeRef ref);

/**
* Reload code in active tab
*/
void reloadActiveTab();

/**
* Reload code in all open tabs
*/
void reloadAllTabs();

/**
* Save node rename in a project and run all needed UI updates
*/
void applyNodeRename(ICodeNodeRef node);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package jadx.api.plugins.pass.impl;

import java.util.function.Consumer;

import jadx.api.JadxDecompiler;
import jadx.api.plugins.pass.JadxPassInfo;
import jadx.api.plugins.pass.types.JadxAfterLoadPass;

public class SimpleAfterLoadPass implements JadxAfterLoadPass {

private final JadxPassInfo info;
private final Consumer<JadxDecompiler> init;

public SimpleAfterLoadPass(String name, Consumer<JadxDecompiler> init) {
this.info = new SimpleJadxPassInfo(name);
this.init = init;
}

@Override
public JadxPassInfo getInfo() {
return info;
}

@Override
public void init(JadxDecompiler decompiler) {
init.accept(decompiler);
}
}
178 changes: 178 additions & 0 deletions jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package jadx.gui.events.services;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.api.JadxDecompiler;
import jadx.api.JavaNode;
import jadx.api.data.ICodeRename;
import jadx.api.data.impl.JadxCodeData;
import jadx.api.plugins.events.JadxEvents;
import jadx.api.plugins.events.types.NodeRenamedByUser;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.settings.JadxProject;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JRenameNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.TabbedPane;
import jadx.gui.ui.codearea.ClassCodeContentPanel;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;

/**
* Rename service listen for user rename events.
* For each event:
* - add/update rename entry in project code data
* - update code and/or invalidate cache for related classes
* - apply all needed UI updates (tabs, classes tree)
*/
public class RenameService {
private static final Logger LOG = LoggerFactory.getLogger(RenameService.class);

public static void init(MainWindow mainWindow) {
RenameService renameService = new RenameService(mainWindow);
mainWindow.events().addListener(JadxEvents.NODE_RENAMED_BY_USER, renameService::process);
}

private final MainWindow mainWindow;

private RenameService(MainWindow mainWindow) {
this.mainWindow = mainWindow;
}

private void process(NodeRenamedByUser event) {
try {
LOG.debug("Applying rename event: {}", event);
JRenameNode node = getRenameNode(event);
updateCodeRenames(set -> processRename(node, event.getNewName(), set));
refreshState(node);
} catch (Exception e) {
LOG.error("Rename failed", e);
UiUtils.errorMessage(mainWindow, "Rename failed:\n" + Utils.getStackTrace(e));
}
}

private @NotNull JRenameNode getRenameNode(NodeRenamedByUser event) {
Object renameNode = event.getRenameNode();
if (renameNode instanceof JRenameNode) {
return (JRenameNode) renameNode;
}
JadxDecompiler decompiler = mainWindow.getWrapper().getDecompiler();
JavaNode javaNode = decompiler.getJavaNodeByRef(event.getNode());
if (javaNode != null) {
JNode node = mainWindow.getCacheObject().getNodeCache().makeFrom(javaNode);
if (node instanceof JRenameNode) {
return (JRenameNode) node;
}
}
throw new JadxRuntimeException("Failed to resolve node: " + event.getNode());
}

private void processRename(JRenameNode node, String newName, Set<ICodeRename> renames) {
ICodeRename rename = node.buildCodeRename(newName, renames);
renames.remove(rename);
if (newName.isEmpty()) {
node.removeAlias();
} else {
renames.add(rename);
}
}

private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) {
JadxProject project = mainWindow.getProject();
JadxCodeData codeData = project.getCodeData();
if (codeData == null) {
codeData = new JadxCodeData();
}
Set<ICodeRename> set = new HashSet<>(codeData.getRenames());
updater.accept(set);
List<ICodeRename> list = new ArrayList<>(set);
Collections.sort(list);
codeData.setRenames(list);
project.setCodeData(codeData);
}

private void refreshState(JRenameNode node) {
List<JavaNode> toUpdate = new ArrayList<>();
node.addUpdateNodes(toUpdate);

JNodeCache nodeCache = mainWindow.getCacheObject().getNodeCache();
Set<JClass> updatedTopClasses = toUpdate
.stream()
.map(JavaNode::getTopParentClass)
.map(nodeCache::makeFrom)
.filter(Objects::nonNull)
.collect(Collectors.toSet());

LOG.debug("Classes to update: {}", updatedTopClasses);
if (updatedTopClasses.isEmpty()) {
return;
}
mainWindow.getBackgroundExecutor().execute("Refreshing",
() -> {
mainWindow.getWrapper().reloadCodeData();
UiUtils.uiRunAndWait(() -> refreshTabs(mainWindow.getTabbedPane(), updatedTopClasses));
refreshClasses(updatedTopClasses);
},
(status) -> {
if (status == TaskStatus.CANCEL_BY_MEMORY) {
mainWindow.showHeapUsageBar();
UiUtils.errorMessage(mainWindow, NLS.str("message.memoryLow"));
}
node.reload(mainWindow);
});
}

private void refreshClasses(Set<JClass> updatedTopClasses) {
CacheObject cache = mainWindow.getCacheObject();
if (updatedTopClasses.size() < 10) {
// small batch => reload
LOG.debug("Classes to reload: {}", updatedTopClasses.size());
for (JClass cls : updatedTopClasses) {
try {
cls.reload(cache);
} catch (Exception e) {
LOG.error("Failed to reload class: {}", cls.getFullName(), e);
}
}
} else {
// big batch => unload
LOG.debug("Classes to unload: {}", updatedTopClasses.size());
for (JClass cls : updatedTopClasses) {
try {
cls.unload(cache);
} catch (Exception e) {
LOG.error("Failed to unload class: {}", cls.getFullName(), e);
}
}
}
}

private void refreshTabs(TabbedPane tabbedPane, Set<JClass> updatedClasses) {
for (ContentPanel tab : tabbedPane.getTabs()) {
JClass rootClass = tab.getNode().getRootClass();
if (updatedClasses.remove(rootClass)) {
ClassCodeContentPanel contentPanel = (ClassCodeContentPanel) tab;
CodeArea codeArea = (CodeArea) contentPanel.getJavaCodePanel().getCodeArea();
codeArea.refreshClass();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import jadx.api.JadxDecompiler;
import jadx.api.JavaClass;
import jadx.api.JavaNode;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.events.types.NodeRenamedByUser;
import jadx.api.plugins.gui.ISettingsGroup;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.gui.JadxGuiSettings;
import jadx.core.dex.nodes.ClassNode;
import jadx.core.dex.nodes.FieldNode;
import jadx.core.dex.nodes.MethodNode;
import jadx.core.plugins.PluginContext;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.codearea.AbstractCodeContentPanel;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.ui.panel.ContentPanel;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.UiUtils;

Expand Down Expand Up @@ -176,4 +183,45 @@ public boolean open(ICodeNodeRef ref) {
return true;
}

@Override
public void reloadActiveTab() {
UiUtils.uiRun(() -> {
CodeArea codeArea = getCodeArea();
if (codeArea != null) {
codeArea.refreshClass();
}
});
}

@Override
public void reloadAllTabs() {
UiUtils.uiRun(() -> {
for (ContentPanel contentPane : commonContext.getMainWindow().getTabbedPane().getTabs()) {
if (contentPane instanceof AbstractCodeContentPanel) {
AbstractCodeArea codeArea = ((AbstractCodeContentPanel) contentPane).getCodeArea();
if (codeArea instanceof CodeArea) {
((CodeArea) codeArea).refreshClass();
}
}
}
});
}

@Override
public void applyNodeRename(ICodeNodeRef nodeRef) {
JadxDecompiler decompiler = commonContext.getMainWindow().getWrapper().getDecompiler();
JavaNode javaNode = decompiler.getJavaNodeByRef(nodeRef);
if (javaNode == null) {
throw new JadxRuntimeException("Failed to resolve node ref: " + nodeRef);
}
String newName;
if (javaNode instanceof JavaClass) {
// package can have alias
newName = javaNode.getFullName();
} else {
newName = javaNode.getName();
}
IJadxEvents events = commonContext.getMainWindow().events();
events.send(new NodeRenamedByUser(nodeRef, "", newName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,7 @@ private void runScript() {
scriptLog.error("Passes reload failed", e);
}
}, taskStatus -> {
tabbedPane.reloadInactiveTabs();
mainWindow.reloadTree();
mainWindow.passesReloaded();
});
}

Expand Down
14 changes: 13 additions & 1 deletion jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
import jadx.gui.JadxWrapper;
import jadx.gui.cache.manager.CacheManager;
import jadx.gui.device.debugger.BreakpointManager;
import jadx.gui.events.services.RenameService;
import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.DecompileTask;
import jadx.gui.jobs.ExportTask;
Expand Down Expand Up @@ -580,7 +581,7 @@ private void onOpen() {
initTree();
updateLiveReload(project.isEnableLiveReload());
BreakpointManager.init(project.getFilePaths().get(0).toAbsolutePath().getParent());
events().addListener(JadxEvents.RELOAD_PROJECT, ev -> UiUtils.uiRun(this::reopen));
initEvents();

List<EditorViewState> openTabs = project.getOpenTabs(this);
backgroundExecutor.execute(NLS.str("progress.load"),
Expand All @@ -593,6 +594,17 @@ private void onOpen() {
});
}

public void passesReloaded() {
initEvents(); // TODO: events reset on reload passes on script run
tabbedPane.reloadInactiveTabs();
reloadTree();
}

private void initEvents() {
events().addListener(JadxEvents.RELOAD_PROJECT, ev -> UiUtils.uiRun(this::reopen));
RenameService.init(this);
}

public void updateLiveReload(boolean state) {
if (liveReloadWorker.isStarted() == state) {
return;
Expand Down
10 changes: 9 additions & 1 deletion jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java
Original file line number Diff line number Diff line change
Expand Up @@ -429,8 +429,16 @@ public void reloadInactiveTabs() {
if (i == current) {
continue;
}
JNode node = ((ContentPanel) getComponentAt(i)).getNode();
ContentPanel oldPanel = (ContentPanel) getComponentAt(i);
EditorViewState viewState = null;
if (oldPanel instanceof IViewStateSupport) {
viewState = ((IViewStateSupport) oldPanel).getEditorViewState();
}
JNode node = oldPanel.getNode();
ContentPanel panel = node.getContentPanel(this);
if (viewState != null && panel instanceof IViewStateSupport) {
((IViewStateSupport) panel).restoreEditorViewState(viewState);
}
FocusManager.listen(panel);
tabsMap.put(node, panel);
setComponentAt(i, panel);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public boolean isActionEnabled(JNode node) {

@Override
public void runAction(JNode node) {
RenameDialog.rename(getCodeArea().getMainWindow(), getCodeArea().getNode(), (JRenameNode) node);
RenameDialog.rename(getCodeArea().getMainWindow(), (JRenameNode) node);
}
}
Loading

0 comments on commit c39a696

Please sign in to comment.