未验证 提交 5d6b8272 编写于 作者: S Skylot

fix(gui): search constant fields usage in all classes (#1801)

上级 78c976ad
......@@ -82,14 +82,22 @@ 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) {
......
......@@ -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=幫助
......
<!-- 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>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册