fix(gui): search constant fields usage in all classes (#1801)
This commit is contained in:
@@ -82,16 +82,24 @@ public class ConstStorage {
|
||||
return;
|
||||
}
|
||||
for (FieldNode f : staticFields) {
|
||||
AccessInfo accFlags = f.getAccessFlags();
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
EncodedValue constVal = f.get(JadxAttrType.CONSTANT_VALUE);
|
||||
if (constVal != null && constVal.getValue() != null) {
|
||||
addConstField(cls, f, constVal.getValue(), accFlags.isPublic());
|
||||
}
|
||||
Object value = getFieldConstValue(f);
|
||||
if (value != null) {
|
||||
addConstField(cls, f, value, f.getAccessFlags().isPublic());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable Object getFieldConstValue(FieldNode fld) {
|
||||
AccessInfo accFlags = fld.getAccessFlags();
|
||||
if (accFlags.isStatic() && accFlags.isFinal()) {
|
||||
EncodedValue constVal = fld.get(JadxAttrType.CONSTANT_VALUE);
|
||||
if (constVal != null) {
|
||||
return constVal.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void removeForClass(ClassNode cls) {
|
||||
classes.remove(cls);
|
||||
globalValues.removeForCls(cls);
|
||||
|
||||
@@ -139,11 +139,13 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
}
|
||||
|
||||
private void dumpBatchesStats(List<JavaClass> classes, List<List<JavaClass>> result, List<DepInfo> deps) {
|
||||
int clsInBatches = result.stream().mapToInt(List::size).sum();
|
||||
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
|
||||
int maxSingleDeps = classes.stream().mapToInt(JavaClass::getTotalDepsCount).max().orElse(-1);
|
||||
int maxSubDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
|
||||
LOG.info("Batches stats:"
|
||||
+ "\n input classes: " + classes.size()
|
||||
+ ",\n classes in batches: " + clsInBatches
|
||||
+ ",\n batches: " + result.size()
|
||||
+ ",\n average batch size: " + String.format("%.2f", avg)
|
||||
+ ",\n max single deps count: " + maxSingleDeps
|
||||
|
||||
@@ -20,7 +20,6 @@ import javax.swing.SwingWorker;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.panel.ProgressPanel;
|
||||
@@ -60,14 +59,6 @@ public class BackgroundExecutor {
|
||||
return taskWorker;
|
||||
}
|
||||
|
||||
public TaskStatus executeAndWait(IBackgroundTask task) {
|
||||
try {
|
||||
return execute(task).get();
|
||||
} catch (Exception e) {
|
||||
throw new JadxRuntimeException("Task execution error", e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void cancelAll() {
|
||||
try {
|
||||
taskRunning.values().forEach(Cancelable::cancel);
|
||||
|
||||
@@ -5,12 +5,15 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
@@ -23,14 +26,16 @@ public class DecompileTask extends CancelableBackgroundTask {
|
||||
return classCount * CLS_LIMIT + 5000;
|
||||
}
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private final JadxWrapper wrapper;
|
||||
private final AtomicInteger complete = new AtomicInteger(0);
|
||||
private int expectedCompleteCount;
|
||||
|
||||
private ProcessResult result;
|
||||
|
||||
public DecompileTask(JadxWrapper wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
public DecompileTask(MainWindow mainWindow) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.wrapper = mainWindow.getWrapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,6 +45,10 @@ public class DecompileTask extends CancelableBackgroundTask {
|
||||
|
||||
@Override
|
||||
public List<Runnable> scheduleJobs() {
|
||||
if (mainWindow.getCacheObject().isFullDecompilationFinished()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<JavaClass> classes = wrapper.getIncludedClasses();
|
||||
expectedCompleteCount = classes.size();
|
||||
complete.set(0);
|
||||
@@ -87,7 +96,41 @@ public class DecompileTask extends CancelableBackgroundTask {
|
||||
+ ", time limit:{ total: " + timeLimit + "ms, per cls: " + CLS_LIMIT + "ms }"
|
||||
+ ", status: " + taskInfo.getStatus());
|
||||
}
|
||||
this.result = new ProcessResult(skippedCls, taskInfo.getStatus(), timeLimit);
|
||||
result = new ProcessResult(skippedCls, taskInfo.getStatus(), timeLimit);
|
||||
|
||||
wrapper.unloadClasses();
|
||||
processDecompilationResults();
|
||||
System.gc();
|
||||
|
||||
mainWindow.getCacheObject().setFullDecompilationFinished(skippedCls == 0);
|
||||
}
|
||||
|
||||
private void processDecompilationResults() {
|
||||
int skippedCls = result.getSkipped();
|
||||
if (skippedCls == 0) {
|
||||
return;
|
||||
}
|
||||
TaskStatus status = result.getStatus();
|
||||
LOG.warn("Decompile and indexing of some classes skipped: {}, status: {}", skippedCls, status);
|
||||
switch (status) {
|
||||
case CANCEL_BY_USER: {
|
||||
String reason = NLS.str("message.userCancelTask");
|
||||
String message = NLS.str("message.indexIncomplete", reason, skippedCls);
|
||||
JOptionPane.showMessageDialog(mainWindow, message);
|
||||
break;
|
||||
}
|
||||
case CANCEL_BY_TIMEOUT: {
|
||||
String reason = NLS.str("message.taskTimeout", result.getTimeLimit());
|
||||
String message = NLS.str("message.indexIncomplete", reason, skippedCls);
|
||||
JOptionPane.showMessageDialog(mainWindow, message);
|
||||
break;
|
||||
}
|
||||
case CANCEL_BY_MEMORY: {
|
||||
mainWindow.showHeapUsageBar();
|
||||
JOptionPane.showMessageDialog(mainWindow, NLS.str("message.indexingClassesSkipped", skippedCls));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -95,7 +95,6 @@ import jadx.gui.device.debugger.BreakpointManager;
|
||||
import jadx.gui.jobs.BackgroundExecutor;
|
||||
import jadx.gui.jobs.DecompileTask;
|
||||
import jadx.gui.jobs.ExportTask;
|
||||
import jadx.gui.jobs.ProcessResult;
|
||||
import jadx.gui.jobs.TaskStatus;
|
||||
import jadx.gui.plugins.mappings.MappingExporter;
|
||||
import jadx.gui.plugins.quark.QuarkDialog;
|
||||
@@ -173,6 +172,7 @@ public class MainWindow extends JFrame {
|
||||
private static final ImageIcon ICON_QUARK = UiUtils.openSvgIcon("ui/quark");
|
||||
private static final ImageIcon ICON_PREF = UiUtils.openSvgIcon("ui/settings");
|
||||
private static final ImageIcon ICON_DEOBF = UiUtils.openSvgIcon("ui/helmChartLock");
|
||||
private static final ImageIcon ICON_DECOMPILE_ALL = UiUtils.openSvgIcon("ui/runAll");
|
||||
private static final ImageIcon ICON_LOG = UiUtils.openSvgIcon("ui/logVerbose");
|
||||
private static final ImageIcon ICON_INFO = UiUtils.openSvgIcon("ui/showInfos");
|
||||
private static final ImageIcon ICON_DEBUGGER = UiUtils.openSvgIcon("ui/startDebugger");
|
||||
@@ -608,54 +608,17 @@ public class MainWindow extends JFrame {
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
waitDecompileTask();
|
||||
requestFullDecompilation();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private static final Object DECOMPILER_TASK_SYNC = new Object();
|
||||
|
||||
public void waitDecompileTask() {
|
||||
synchronized (DECOMPILER_TASK_SYNC) {
|
||||
try {
|
||||
DecompileTask decompileTask = new DecompileTask(wrapper);
|
||||
backgroundExecutor.executeAndWait(decompileTask);
|
||||
backgroundExecutor.execute(decompileTask.getTitle(), wrapper::unloadClasses).get();
|
||||
processDecompilationResults(decompileTask.getResult());
|
||||
System.gc();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Decompile task execution failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processDecompilationResults(ProcessResult decompile) {
|
||||
int skippedCls = decompile.getSkipped();
|
||||
if (skippedCls == 0) {
|
||||
public void requestFullDecompilation() {
|
||||
if (cacheObject.isFullDecompilationFinished()) {
|
||||
return;
|
||||
}
|
||||
TaskStatus status = decompile.getStatus();
|
||||
LOG.warn("Decompile and indexing of some classes skipped: {}, status: {}", skippedCls, status);
|
||||
switch (status) {
|
||||
case CANCEL_BY_USER: {
|
||||
String reason = NLS.str("message.userCancelTask");
|
||||
String message = NLS.str("message.indexIncomplete", reason, skippedCls);
|
||||
JOptionPane.showMessageDialog(this, message);
|
||||
break;
|
||||
}
|
||||
case CANCEL_BY_TIMEOUT: {
|
||||
String reason = NLS.str("message.taskTimeout", decompile.getTimeLimit());
|
||||
String message = NLS.str("message.indexIncomplete", reason, skippedCls);
|
||||
JOptionPane.showMessageDialog(this, message);
|
||||
break;
|
||||
}
|
||||
case CANCEL_BY_MEMORY: {
|
||||
showHeapUsageBar();
|
||||
JOptionPane.showMessageDialog(this, NLS.str("message.indexingClassesSkipped", skippedCls));
|
||||
break;
|
||||
}
|
||||
}
|
||||
backgroundExecutor.execute(new DecompileTask(this));
|
||||
}
|
||||
|
||||
public void cancelBackgroundJobs() {
|
||||
@@ -1041,6 +1004,10 @@ public class MainWindow extends JFrame {
|
||||
commentSearchAction.putValue(Action.ACCELERATOR_KEY, getKeyStroke(KeyEvent.VK_SEMICOLON,
|
||||
UiUtils.ctrlButton() | KeyEvent.SHIFT_DOWN_MASK));
|
||||
|
||||
ActionHandler decompileAllAction = new ActionHandler(ev -> requestFullDecompilation());
|
||||
decompileAllAction.setNameAndDesc(NLS.str("menu.decompile_all"));
|
||||
decompileAllAction.setIcon(ICON_DECOMPILE_ALL);
|
||||
|
||||
Action deobfAction = new AbstractAction(NLS.str("menu.deobfuscation"), ICON_DEOBF) {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
@@ -1152,6 +1119,7 @@ public class MainWindow extends JFrame {
|
||||
|
||||
JMenu tools = new JMenu(NLS.str("menu.tools"));
|
||||
tools.setMnemonic(KeyEvent.VK_T);
|
||||
tools.add(decompileAllAction);
|
||||
tools.add(deobfMenuItem);
|
||||
tools.add(quarkAction);
|
||||
tools.add(openDeviceAction);
|
||||
@@ -1231,6 +1199,7 @@ public class MainWindow extends JFrame {
|
||||
exportAction.setEnabled(loaded);
|
||||
saveProjectAsAction.setEnabled(loaded);
|
||||
reload.setEnabled(loaded);
|
||||
decompileAllAction.setEnabled(loaded);
|
||||
deobfAction.setEnabled(loaded);
|
||||
quarkAction.setEnabled(loaded);
|
||||
return false;
|
||||
|
||||
@@ -19,11 +19,14 @@ import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.utils.CodeUtils;
|
||||
import jadx.core.dex.info.ConstStorage;
|
||||
import jadx.core.dex.nodes.FieldNode;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.jobs.TaskStatus;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.CodeNode;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JField;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -51,6 +54,7 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
@Override
|
||||
protected void openInit() {
|
||||
progressStartCommon();
|
||||
prepareUsageData();
|
||||
mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"),
|
||||
this::collectUsageData,
|
||||
(status) -> {
|
||||
@@ -63,26 +67,39 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
});
|
||||
}
|
||||
|
||||
private void prepareUsageData() {
|
||||
if (mainWindow.getSettings().isReplaceConsts() && node instanceof JField) {
|
||||
FieldNode fld = ((JField) node).getJavaField().getFieldNode();
|
||||
boolean constField = ConstStorage.getFieldConstValue(fld) != null;
|
||||
if (constField && !fld.getAccessFlags().isPrivate()) {
|
||||
// run full decompilation to prepare for full code scan
|
||||
mainWindow.requestFullDecompilation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectUsageData() {
|
||||
usageList = new ArrayList<>();
|
||||
Map<JavaNode, List<JavaNode>> usageQuery = buildUsageQuery();
|
||||
usageQuery.forEach((searchNode, useNodes) -> useNodes.stream()
|
||||
.map(JavaNode::getTopParentClass)
|
||||
.distinct()
|
||||
.forEach(u -> processUsage(searchNode, u)));
|
||||
buildUsageQuery().forEach(
|
||||
(searchNode, useNodes) -> useNodes.stream()
|
||||
.map(JavaNode::getTopParentClass)
|
||||
.distinct()
|
||||
.forEach(u -> processUsage(searchNode, u)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return mapping of 'node to search' to 'use places'
|
||||
*/
|
||||
private Map<JavaNode, List<JavaNode>> buildUsageQuery() {
|
||||
Map<JavaNode, List<JavaNode>> map = new HashMap<>();
|
||||
private Map<JavaNode, List<? extends JavaNode>> buildUsageQuery() {
|
||||
Map<JavaNode, List<? extends JavaNode>> map = new HashMap<>();
|
||||
if (node instanceof JMethod) {
|
||||
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
|
||||
for (JavaMethod mth : getMethodWithOverrides(javaMethod)) {
|
||||
map.put(mth, mth.getUseIn());
|
||||
}
|
||||
} else if (node instanceof JClass) {
|
||||
return map;
|
||||
}
|
||||
if (node instanceof JClass) {
|
||||
JavaClass javaCls = ((JClass) node).getCls();
|
||||
map.put(javaCls, javaCls.getUseIn());
|
||||
// add constructors usage into class usage
|
||||
@@ -91,10 +108,19 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
map.put(javaMth, javaMth.getUseIn());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
map.put(javaNode, javaNode.getUseIn());
|
||||
return map;
|
||||
}
|
||||
if (node instanceof JField && mainWindow.getSettings().isReplaceConsts()) {
|
||||
FieldNode fld = ((JField) node).getJavaField().getFieldNode();
|
||||
boolean constField = ConstStorage.getFieldConstValue(fld) != null;
|
||||
if (constField && !fld.getAccessFlags().isPrivate()) {
|
||||
// search all classes to collect usage of replaced constants
|
||||
map.put(fld.getJavaNode(), mainWindow.getWrapper().getIncludedClasses());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
map.put(javaNode, javaNode.getUseIn());
|
||||
return map;
|
||||
}
|
||||
|
||||
@@ -108,9 +134,12 @@ public class UsageDialog extends CommonSearchDialog {
|
||||
|
||||
private void processUsage(JavaNode searchNode, JavaClass topUseClass) {
|
||||
ICodeInfo codeInfo = topUseClass.getCodeInfo();
|
||||
List<Integer> usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode);
|
||||
if (usePositions.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String code = codeInfo.getCodeStr();
|
||||
JadxWrapper wrapper = mainWindow.getWrapper();
|
||||
List<Integer> usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode);
|
||||
for (int pos : usePositions) {
|
||||
String line = CodeUtils.getLineForPos(code, pos);
|
||||
if (line.startsWith("import ")) {
|
||||
|
||||
@@ -18,6 +18,8 @@ public class CacheObject {
|
||||
|
||||
private List<List<JavaClass>> decompileBatches;
|
||||
|
||||
private volatile boolean fullDecompilationFinished;
|
||||
|
||||
public CacheObject() {
|
||||
reset();
|
||||
}
|
||||
@@ -27,6 +29,7 @@ public class CacheObject {
|
||||
jNodeCache = new JNodeCache();
|
||||
lastSearchOptions = new HashMap<>();
|
||||
decompileBatches = null;
|
||||
fullDecompilationFinished = false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -53,4 +56,12 @@ public class CacheObject {
|
||||
public void setDecompileBatches(List<List<JavaClass>> decompileBatches) {
|
||||
this.decompileBatches = decompileBatches;
|
||||
}
|
||||
|
||||
public boolean isFullDecompilationFinished() {
|
||||
return fullDecompilationFinished;
|
||||
}
|
||||
|
||||
public void setFullDecompilationFinished(boolean fullDecompilationFinished) {
|
||||
this.fullDecompilationFinished = fullDecompilationFinished;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Textsuche
|
||||
menu.class_search=Klassen-Suche
|
||||
menu.comment_search=Kommentar suchen
|
||||
menu.tools=Tools
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Deobfuskierung
|
||||
menu.log=Log-Anzeige
|
||||
menu.help=Hilfe
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Text search
|
||||
menu.class_search=Class search
|
||||
menu.comment_search=Comment searchF
|
||||
menu.tools=Tools
|
||||
menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Deobfuscation
|
||||
menu.log=Log Viewer
|
||||
menu.help=Help
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Buscar texto
|
||||
menu.class_search=Buscar clase
|
||||
#menu.comment_search=Comment search
|
||||
menu.tools=Herramientas
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Desofuscación
|
||||
menu.log=Visor log
|
||||
menu.help=Ayuda
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=텍스트 검색
|
||||
menu.class_search=클래스 검색
|
||||
menu.comment_search=주석 검색
|
||||
menu.tools=도구
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=난독화 해제
|
||||
menu.log=로그 뷰어
|
||||
menu.help=도움말
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Buscar por texto
|
||||
menu.class_search=Buscar por classe
|
||||
menu.comment_search=Busca por comentário
|
||||
menu.tools=Ferramentas
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Desofuscar
|
||||
menu.log=Visualizador de log
|
||||
menu.help=Ajuda
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=Поиск строк
|
||||
menu.class_search=Поиск классов
|
||||
menu.comment_search=Поиск комментариев
|
||||
menu.tools=Инструменты
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=Деобфускация
|
||||
menu.log=Просмотр логов
|
||||
menu.help=Помощь
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=文本搜索
|
||||
menu.class_search=类名搜索
|
||||
menu.comment_search=注释搜索
|
||||
menu.tools=工具
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=反混淆
|
||||
menu.log=日志查看器
|
||||
menu.help=帮助
|
||||
|
||||
@@ -14,6 +14,7 @@ menu.text_search=文字搜尋
|
||||
menu.class_search=類別搜尋
|
||||
menu.comment_search=註解搜尋
|
||||
menu.tools=工具
|
||||
#menu.decompile_all=Decompile all classes
|
||||
menu.deobfuscation=去模糊化
|
||||
menu.log=日誌檢視器
|
||||
menu.help=幫助
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<!-- Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<g fill="none" fill-rule="evenodd">
|
||||
<polygon fill="#59A869" points="2 2 10 8 2 14"/>
|
||||
<path fill="#59A869" d="M7,11.75 L12,8 L7,4.25 L7,2 L15,8 L7,14 L7,11.75 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 436 B |
Reference in New Issue
Block a user