diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java index 7968bf93206cf216d182d39c64ea9bd79b29b9cb..ed6b224b05752e5eede1cbe722e89a763ef0218a 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ConstStorage.java @@ -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) { diff --git a/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java b/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java index 26e076c26a4bf0973220b63d48a7bbb544619a07..522888ccbef6b8bf4fbe0b81cbb1a51909042bf2 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java +++ b/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java @@ -139,11 +139,13 @@ public class DecompilerScheduler implements IDecompileScheduler { } private void dumpBatchesStats(List classes, List> result, List 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 diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java index 8c1e0b779305808ddc450065b5caf1183c852f48..114da9b342102a36f587dbc92a15e59bb9ef5887 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundExecutor.java @@ -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); diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java b/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java index b288df299f2a773e49141ded83037ac02043ed01..01bd04d6fc2925c0ceea719310082017907d942b 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/DecompileTask.java @@ -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 scheduleJobs() { + if (mainWindow.getCacheObject().isFullDecompilationFinished()) { + return Collections.emptyList(); + } + List 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 diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index e6bea223a20311a612f4149b4a0b28a7f8b1ba64..9f4b51b6cb1fd8069e3e12a5809546974b1bccfa 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -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; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java index 57014b2a5e620a4b4fe373e5f8e0dea34fbdf18d..084583df7c2b0887eb7544f31c4a3aa63d4071fd 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/UsageDialog.java @@ -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> 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> buildUsageQuery() { - Map> map = new HashMap<>(); + private Map> buildUsageQuery() { + Map> 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 usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode); + if (usePositions.isEmpty()) { + return; + } String code = codeInfo.getCodeStr(); JadxWrapper wrapper = mainWindow.getWrapper(); - List usePositions = topUseClass.getUsePlacesFor(codeInfo, searchNode); for (int pos : usePositions) { String line = CodeUtils.getLineForPos(code, pos); if (line.startsWith("import ")) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java index dabee1e02dc8c20ae09ff6e5a9d948c448b051fd..7eb588517983337a9d42d1ebce57399acb3a1e3b 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -18,6 +18,8 @@ public class CacheObject { private List> 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> decompileBatches) { this.decompileBatches = decompileBatches; } + + public boolean isFullDecompilationFinished() { + return fullDecompilationFinished; + } + + public void setFullDecompilationFinished(boolean fullDecompilationFinished) { + this.fullDecompilationFinished = fullDecompilationFinished; + } } diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index 9800ca4fab5be718bc785a6a5bbd16d3fa46c4bb..c2d0c5d6110fe98a2aab2ee5718c08c2841a5475 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index babdb80fc5dc2994d6151f342985ceb7068fdebd..f0db06ce5b335eaf923402d983b07d2faded5842 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 3772a46163ffcf9ec318efffb2d01cd1e98da91a..3c0bb25f228bf6e04e8ef62aa0c46b2b23ce1b30 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 2fee02a4355223474e89cf4ab0842e73bc955c87..e1834ae04fde0c204c50d60555ddda5061aa20b9 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -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=도움말 diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index 4d9301ba045ba9c89329873bae49d13c91e34142..260b937c68569843b91aa4cc8ed16ce6c357a194 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index 4847c843e1b4a72219c04923d2c789eacb8c2903..487ce468a9c8af18fff01c8bc72b2ddff9477889 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -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=Помощь diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 71a16b8160f665353b5e4628944761104a08e422..4beea1cf9cb4f0c4b30b0f148f81826690e0d54d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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=帮助 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index ca81942a7f16be29c6b471bfdcc57977d72a391f..fa787b22aa98652445c7c5a0f6fe83621dddbaaf 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -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=幫助 diff --git a/jadx-gui/src/main/resources/icons/ui/runAll.svg b/jadx-gui/src/main/resources/icons/ui/runAll.svg new file mode 100644 index 0000000000000000000000000000000000000000..51c8bc7557dcc2319b7fb40b32fa6f0c5ab8fdb7 --- /dev/null +++ b/jadx-gui/src/main/resources/icons/ui/runAll.svg @@ -0,0 +1,7 @@ + + + + + + +