fix(gui): add memory limit checks to export and load tasks (#1181)
This commit is contained in:
@@ -207,16 +207,24 @@ public final class JadxDecompiler implements Closeable {
|
||||
return getSaveExecutor(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
|
||||
public List<Runnable> getSaveTasks() {
|
||||
return getSaveTasks(!args.isSkipSources(), !args.isSkipResources());
|
||||
}
|
||||
|
||||
private ExecutorService getSaveExecutor(boolean saveSources, boolean saveResources) {
|
||||
int threadsCount = args.getThreadsCount();
|
||||
LOG.debug("processing threads count: {}", threadsCount);
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
List<Runnable> tasks = getSaveTasks(saveSources, saveResources);
|
||||
tasks.forEach(executor::execute);
|
||||
return executor;
|
||||
}
|
||||
|
||||
private List<Runnable> getSaveTasks(boolean saveSources, boolean saveResources) {
|
||||
if (root == null) {
|
||||
throw new JadxRuntimeException("No loaded files");
|
||||
}
|
||||
int threadsCount = args.getThreadsCount();
|
||||
LOG.debug("processing threads count: {}", threadsCount);
|
||||
|
||||
LOG.info("processing ...");
|
||||
ExecutorService executor = Executors.newFixedThreadPool(threadsCount);
|
||||
|
||||
File sourcesOutDir;
|
||||
File resOutDir;
|
||||
if (args.isExportAsGradleProject()) {
|
||||
@@ -244,16 +252,17 @@ public final class JadxDecompiler implements Closeable {
|
||||
sourcesOutDir = args.getOutDirSrc();
|
||||
resOutDir = args.getOutDirRes();
|
||||
}
|
||||
if (saveResources) {
|
||||
appendResourcesSave(executor, resOutDir);
|
||||
}
|
||||
List<Runnable> tasks = new ArrayList<>();
|
||||
if (saveSources) {
|
||||
appendSourcesSave(executor, sourcesOutDir);
|
||||
appendSourcesSave(tasks, sourcesOutDir);
|
||||
}
|
||||
return executor;
|
||||
if (saveResources) {
|
||||
appendResourcesSaveTasks(tasks, resOutDir);
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
private void appendResourcesSave(ExecutorService executor, File outDir) {
|
||||
private void appendResourcesSaveTasks(List<Runnable> tasks, File outDir) {
|
||||
Set<String> inputFileNames = args.getInputFiles().stream().map(File::getAbsolutePath).collect(Collectors.toSet());
|
||||
for (ResourceFile resourceFile : getResources()) {
|
||||
if (resourceFile.getType() != ResourceType.ARSC
|
||||
@@ -261,11 +270,11 @@ public final class JadxDecompiler implements Closeable {
|
||||
// ignore resource made from input file
|
||||
continue;
|
||||
}
|
||||
executor.execute(new ResourcesSaver(outDir, resourceFile));
|
||||
tasks.add(new ResourcesSaver(outDir, resourceFile));
|
||||
}
|
||||
}
|
||||
|
||||
private void appendSourcesSave(ExecutorService executor, File outDir) {
|
||||
private void appendSourcesSave(List<Runnable> tasks, File outDir) {
|
||||
Predicate<String> classFilter = args.getClassFilter();
|
||||
for (JavaClass cls : getClasses()) {
|
||||
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
|
||||
@@ -274,7 +283,7 @@ public final class JadxDecompiler implements Closeable {
|
||||
if (classFilter != null && !classFilter.test(cls.getFullName())) {
|
||||
continue;
|
||||
}
|
||||
executor.execute(() -> {
|
||||
tasks.add(() -> {
|
||||
try {
|
||||
ICodeInfo code = cls.getCodeInfo();
|
||||
SaveCode.save(outDir, cls.getClassNode(), code);
|
||||
|
||||
@@ -29,6 +29,6 @@ public class InMemoryCodeCache implements ICodeCache {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InMemoryCodeCache";
|
||||
return "InMemoryCodeCache: size=" + storage.size();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import jadx.core.dex.visitors.typeinference.TypeUpdate;
|
||||
import jadx.core.utils.CacheStorage;
|
||||
import jadx.core.utils.ErrorsCounter;
|
||||
import jadx.core.utils.StringUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.android.AndroidResourcesUtils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.core.xmlgen.ResTableParser;
|
||||
@@ -61,8 +62,6 @@ public class RootNode {
|
||||
private final MethodUtils methodUtils;
|
||||
private final TypeUtils typeUtils;
|
||||
|
||||
private final ICodeCache codeCache;
|
||||
|
||||
private final Map<ClassInfo, ClassNode> clsMap = new HashMap<>();
|
||||
private List<ClassNode> classes = new ArrayList<>();
|
||||
|
||||
@@ -80,7 +79,6 @@ public class RootNode {
|
||||
this.stringUtils = new StringUtils(args);
|
||||
this.constValues = new ConstStorage(args);
|
||||
this.typeUpdate = new TypeUpdate(this);
|
||||
this.codeCache = args.getCodeCache();
|
||||
this.methodUtils = new MethodUtils(this);
|
||||
this.typeUtils = new TypeUtils(this);
|
||||
this.isProto = args.getInputFiles().size() > 0 && args.getInputFiles().get(0).getName().toLowerCase().endsWith(".aab");
|
||||
@@ -94,6 +92,7 @@ public class RootNode {
|
||||
} catch (Exception e) {
|
||||
addDummyClass(cls, e);
|
||||
}
|
||||
Utils.checkThreadInterrupt();
|
||||
});
|
||||
}
|
||||
if (classes.size() != clsMap.size()) {
|
||||
@@ -498,7 +497,7 @@ public class RootNode {
|
||||
}
|
||||
|
||||
public ICodeCache getCodeCache() {
|
||||
return codeCache;
|
||||
return args.getCodeCache();
|
||||
}
|
||||
|
||||
public MethodUtils getMethodUtils() {
|
||||
|
||||
@@ -19,6 +19,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import jadx.api.ICodeWriter;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.core.dex.visitors.DepthTraversal;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
|
||||
public class Utils {
|
||||
|
||||
@@ -373,4 +374,10 @@ public class Utils {
|
||||
public static <T> boolean notEmpty(T[] arr) {
|
||||
return arr != null && arr.length != 0;
|
||||
}
|
||||
|
||||
public static void checkThreadInterrupt() {
|
||||
if (Thread.interrupted()) {
|
||||
throw new JadxRuntimeException("Thread interrupted");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package jadx.gui;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -8,8 +7,6 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.ProgressMonitor;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -63,16 +60,6 @@ public class JadxWrapper {
|
||||
this.openPaths = Collections.emptyList();
|
||||
}
|
||||
|
||||
public void saveAll(File dir, ProgressMonitor progressMonitor) {
|
||||
Runnable save = () -> {
|
||||
decompiler.getArgs().setRootDir(dir);
|
||||
decompiler.save(500, (done, total) -> progressMonitor.setProgress((int) (done * 100.0 / total)));
|
||||
progressMonitor.close();
|
||||
LOG.info("done");
|
||||
};
|
||||
new Thread(save).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete list of classes
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,8 @@ import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.swing.SwingWorker;
|
||||
|
||||
@@ -57,12 +58,12 @@ public class BackgroundExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
public void execute(String title, List<Runnable> backgroundJobs, Runnable onFinishUiRunnable) {
|
||||
public void execute(String title, List<Runnable> backgroundJobs, Consumer<TaskStatus> onFinishUiRunnable) {
|
||||
execute(new SimpleTask(title, backgroundJobs, onFinishUiRunnable));
|
||||
}
|
||||
|
||||
public void execute(String title, Runnable backgroundRunnable, Runnable onFinishUiRunnable) {
|
||||
execute(new SimpleTask(title, backgroundRunnable, onFinishUiRunnable));
|
||||
public void execute(String title, Runnable backgroundRunnable, Consumer<TaskStatus> onFinishUiRunnable) {
|
||||
execute(new SimpleTask(title, Collections.singletonList(backgroundRunnable), onFinishUiRunnable));
|
||||
}
|
||||
|
||||
private ThreadPoolExecutor makeTaskQueueExecutor() {
|
||||
@@ -71,8 +72,9 @@ public class BackgroundExecutor {
|
||||
|
||||
private final class TaskWorker extends SwingWorker<TaskStatus, Void> {
|
||||
private final IBackgroundTask task;
|
||||
private long jobsCount;
|
||||
private TaskStatus status = TaskStatus.WAIT;
|
||||
private long jobsCount;
|
||||
private long jobsComplete;
|
||||
|
||||
public TaskWorker(IBackgroundTask task) {
|
||||
this.task = task;
|
||||
@@ -89,13 +91,11 @@ public class BackgroundExecutor {
|
||||
progressPane.changeCancelBtnVisible(this, task.canBeCanceled());
|
||||
progressPane.changeVisibility(this, true);
|
||||
|
||||
if (runJobs()) {
|
||||
status = TaskStatus.COMPLETE;
|
||||
}
|
||||
runJobs();
|
||||
return status;
|
||||
}
|
||||
|
||||
private boolean runJobs() throws InterruptedException {
|
||||
private void runJobs() throws InterruptedException {
|
||||
List<Runnable> jobs = task.scheduleJobs();
|
||||
jobsCount = jobs.size();
|
||||
LOG.debug("Starting background task '{}', jobs count: {}, time limit: {} ms, memory check: {}",
|
||||
@@ -107,33 +107,34 @@ public class BackgroundExecutor {
|
||||
executor.execute(job);
|
||||
}
|
||||
executor.shutdown();
|
||||
return waitTermination(executor);
|
||||
status = waitTermination(executor);
|
||||
jobsComplete = executor.getCompletedTaskCount();
|
||||
}
|
||||
|
||||
@SuppressWarnings("BusyWait")
|
||||
private boolean waitTermination(ThreadPoolExecutor executor) throws InterruptedException {
|
||||
BooleanSupplier cancelCheck = buildCancelCheck();
|
||||
private TaskStatus waitTermination(ThreadPoolExecutor executor) throws InterruptedException {
|
||||
Supplier<TaskStatus> cancelCheck = buildCancelCheck();
|
||||
try {
|
||||
while (true) {
|
||||
if (executor.isTerminated()) {
|
||||
return true;
|
||||
return TaskStatus.COMPLETE;
|
||||
}
|
||||
if (cancelCheck.getAsBoolean()) {
|
||||
TaskStatus cancelStatus = cancelCheck.get();
|
||||
if (cancelStatus != null) {
|
||||
performCancel(executor);
|
||||
return false;
|
||||
return cancelStatus;
|
||||
}
|
||||
setProgress(calcProgress(executor.getCompletedTaskCount()));
|
||||
Thread.sleep(1000);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
LOG.debug("Task wait interrupted");
|
||||
status = TaskStatus.CANCEL_BY_USER;
|
||||
performCancel(executor);
|
||||
return false;
|
||||
return TaskStatus.CANCEL_BY_USER;
|
||||
} catch (Exception e) {
|
||||
LOG.error("Task wait aborted by exception", e);
|
||||
performCancel(executor);
|
||||
return false;
|
||||
return TaskStatus.ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,37 +147,23 @@ public class BackgroundExecutor {
|
||||
LOG.debug("Task cancel complete: {}", complete);
|
||||
}
|
||||
|
||||
private boolean isSimpleTask() {
|
||||
return task.timeLimit() == 0 && !task.checkMemoryUsage();
|
||||
}
|
||||
|
||||
private boolean simpleCancelCheck() {
|
||||
if (isCancelled() || Thread.currentThread().isInterrupted()) {
|
||||
LOG.debug("Task '{}' canceled", task.getTitle());
|
||||
status = TaskStatus.CANCEL_BY_USER;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private BooleanSupplier buildCancelCheck() {
|
||||
if (isSimpleTask()) {
|
||||
return this::simpleCancelCheck;
|
||||
}
|
||||
private Supplier<TaskStatus> buildCancelCheck() {
|
||||
long waitUntilTime = task.timeLimit() == 0 ? 0 : System.currentTimeMillis() + task.timeLimit();
|
||||
boolean checkMemoryUsage = task.checkMemoryUsage();
|
||||
return () -> {
|
||||
if (waitUntilTime != 0 && waitUntilTime < System.currentTimeMillis()) {
|
||||
LOG.debug("Task '{}' execution timeout, force cancel", task.getTitle());
|
||||
status = TaskStatus.CANCEL_BY_TIMEOUT;
|
||||
return true;
|
||||
LOG.error("Task '{}' execution timeout, force cancel", task.getTitle());
|
||||
return TaskStatus.CANCEL_BY_TIMEOUT;
|
||||
}
|
||||
if (checkMemoryUsage && !UiUtils.isFreeMemoryAvailable()) {
|
||||
LOG.debug("Task '{}' memory limit reached, force cancel", task.getTitle());
|
||||
status = TaskStatus.CANCEL_BY_MEMORY;
|
||||
return true;
|
||||
LOG.error("Task '{}' memory limit reached, force cancel", task.getTitle());
|
||||
return TaskStatus.CANCEL_BY_MEMORY;
|
||||
}
|
||||
return simpleCancelCheck();
|
||||
if (isCancelled() || Thread.currentThread().isInterrupted()) {
|
||||
LOG.warn("Task '{}' canceled", task.getTitle());
|
||||
return TaskStatus.CANCEL_BY_USER;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -187,25 +174,21 @@ public class BackgroundExecutor {
|
||||
@Override
|
||||
protected void done() {
|
||||
progressPane.setVisible(false);
|
||||
task.onFinish(status);
|
||||
task.onFinish(status, jobsCount - jobsComplete);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SimpleTask implements IBackgroundTask {
|
||||
private final String title;
|
||||
private final List<Runnable> jobs;
|
||||
private final Runnable onFinish;
|
||||
private final Consumer<TaskStatus> onFinish;
|
||||
|
||||
public SimpleTask(String title, List<Runnable> jobs, @Nullable Runnable onFinish) {
|
||||
public SimpleTask(String title, List<Runnable> jobs, @Nullable Consumer<TaskStatus> onFinish) {
|
||||
this.title = title;
|
||||
this.jobs = jobs;
|
||||
this.onFinish = onFinish;
|
||||
}
|
||||
|
||||
public SimpleTask(String title, Runnable job, @Nullable Runnable onFinish) {
|
||||
this(title, Collections.singletonList(job), onFinish);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
@@ -217,10 +200,15 @@ public class BackgroundExecutor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(TaskStatus status) {
|
||||
public void onFinish(TaskStatus status, long l) {
|
||||
if (onFinish != null) {
|
||||
onFinish.run();
|
||||
onFinish.accept(status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkMemoryUsage() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,10 @@ public class DecompileTask implements IBackgroundTask {
|
||||
|
||||
private static final int CLS_LIMIT = Integer.parseInt(UiUtils.getEnvVar("JADX_CLS_PROCESS_LIMIT", "50"));
|
||||
|
||||
public static int calcDecompileTimeLimit(int classCount) {
|
||||
return classCount * CLS_LIMIT + 5000;
|
||||
}
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private final JadxWrapper wrapper;
|
||||
private final AtomicInteger complete = new AtomicInteger(0);
|
||||
@@ -59,7 +63,7 @@ public class DecompileTask implements IBackgroundTask {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(TaskStatus status) {
|
||||
public void onFinish(TaskStatus status, long skippedJobs) {
|
||||
long taskTime = System.currentTimeMillis() - startTime;
|
||||
long avgPerCls = taskTime / expectedCompleteCount;
|
||||
LOG.info("Decompile task complete in {} ms (avg {} ms per class), classes: {},"
|
||||
@@ -68,29 +72,28 @@ public class DecompileTask implements IBackgroundTask {
|
||||
|
||||
IndexService indexService = mainWindow.getCacheObject().getIndexService();
|
||||
indexService.setComplete(true);
|
||||
|
||||
int complete = this.complete.get();
|
||||
int skipped = expectedCompleteCount - complete;
|
||||
if (skipped == 0) {
|
||||
if (skippedJobs == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.warn("Decompile and indexing of some classes skipped: {}, status: {}", skipped, status);
|
||||
|
||||
int skippedCls = expectedCompleteCount - complete.get();
|
||||
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, skipped);
|
||||
String message = NLS.str("message.indexIncomplete", reason, skippedCls);
|
||||
JOptionPane.showMessageDialog(mainWindow, message);
|
||||
break;
|
||||
}
|
||||
case CANCEL_BY_TIMEOUT: {
|
||||
String reason = NLS.str("message.taskTimeout", timeLimit());
|
||||
String message = NLS.str("message.indexIncomplete", reason, skipped);
|
||||
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", skipped));
|
||||
JOptionPane.showMessageDialog(mainWindow, NLS.str("message.indexingClassesSkipped", skippedCls));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -103,7 +106,7 @@ public class DecompileTask implements IBackgroundTask {
|
||||
|
||||
@Override
|
||||
public int timeLimit() {
|
||||
return expectedCompleteCount * CLS_LIMIT + 5000;
|
||||
return calcDecompileTimeLimit(expectedCompleteCount);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package jadx.gui.jobs;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.JOptionPane;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.FixedCodeCache;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
public class ExportTask implements IBackgroundTask {
|
||||
|
||||
private final MainWindow mainWindow;
|
||||
private final JadxWrapper wrapper;
|
||||
private final File saveDir;
|
||||
|
||||
private int timeLimit;
|
||||
private ICodeCache uiCodeCache;
|
||||
|
||||
public ExportTask(MainWindow mainWindow, JadxWrapper wrapper, File saveDir) {
|
||||
this.mainWindow = mainWindow;
|
||||
this.wrapper = wrapper;
|
||||
this.saveDir = saveDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return NLS.str("msg.saving_sources");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Runnable> scheduleJobs() {
|
||||
wrapCodeCache();
|
||||
JadxDecompiler decompiler = wrapper.getDecompiler();
|
||||
decompiler.getArgs().setRootDir(saveDir);
|
||||
List<Runnable> saveTasks = decompiler.getSaveTasks();
|
||||
this.timeLimit = DecompileTask.calcDecompileTimeLimit(saveTasks.size());
|
||||
return saveTasks;
|
||||
}
|
||||
|
||||
private void wrapCodeCache() {
|
||||
uiCodeCache = wrapper.getArgs().getCodeCache();
|
||||
// do not save newly decompiled code in cache to not increase memory usage
|
||||
// TODO: maybe make memory limited cache?
|
||||
wrapper.getArgs().setCodeCache(new FixedCodeCache(uiCodeCache));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish(TaskStatus status, long skipped) {
|
||||
// restore initial code cache
|
||||
wrapper.getArgs().setCodeCache(uiCodeCache);
|
||||
if (skipped == 0) {
|
||||
return;
|
||||
}
|
||||
String reason = getIncompleteReason(status);
|
||||
if (reason != null) {
|
||||
JOptionPane.showMessageDialog(mainWindow,
|
||||
NLS.str("message.saveIncomplete", reason, skipped),
|
||||
NLS.str("message.errorTitle"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
private String getIncompleteReason(TaskStatus status) {
|
||||
switch (status) {
|
||||
case CANCEL_BY_USER:
|
||||
return NLS.str("message.userCancelTask");
|
||||
|
||||
case CANCEL_BY_TIMEOUT:
|
||||
return NLS.str("message.taskTimeout", timeLimit());
|
||||
|
||||
case CANCEL_BY_MEMORY:
|
||||
mainWindow.showHeapUsageBar();
|
||||
return NLS.str("message.memoryLow");
|
||||
|
||||
case ERROR:
|
||||
return NLS.str("message.taskError");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int timeLimit() {
|
||||
return timeLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeCanceled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkMemoryUsage() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ public interface IBackgroundTask {
|
||||
|
||||
List<Runnable> scheduleJobs();
|
||||
|
||||
void onFinish(TaskStatus status);
|
||||
void onFinish(TaskStatus status, long skipped);
|
||||
|
||||
default boolean canBeCanceled() {
|
||||
return false;
|
||||
|
||||
@@ -11,7 +11,6 @@ import jadx.api.JavaClass;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.CodeLinesInfo;
|
||||
import jadx.gui.utils.CodeUsageInfo;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.search.StringRef;
|
||||
import jadx.gui.utils.search.TextSearchIndex;
|
||||
|
||||
@@ -59,9 +58,7 @@ public class IndexService {
|
||||
}
|
||||
index.remove(cls);
|
||||
usageInfo.remove(cls);
|
||||
if (UiUtils.isFreeMemoryAvailable()) {
|
||||
indexCls(cls);
|
||||
}
|
||||
indexCls(cls);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -6,5 +6,6 @@ public enum TaskStatus {
|
||||
COMPLETE,
|
||||
CANCEL_BY_USER,
|
||||
CANCEL_BY_TIMEOUT,
|
||||
CANCEL_BY_MEMORY
|
||||
CANCEL_BY_MEMORY,
|
||||
ERROR
|
||||
}
|
||||
|
||||
@@ -59,7 +59,6 @@ import javax.swing.JSplitPane;
|
||||
import javax.swing.JToggleButton;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.ProgressMonitor;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.WindowConstants;
|
||||
import javax.swing.event.MenuEvent;
|
||||
@@ -91,6 +90,7 @@ import jadx.gui.JadxWrapper;
|
||||
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.IndexService;
|
||||
import jadx.gui.jobs.TaskStatus;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
@@ -392,7 +392,12 @@ public class MainWindow extends JFrame {
|
||||
}
|
||||
backgroundExecutor.execute(NLS.str("progress.load"),
|
||||
() -> wrapper.openFile(paths),
|
||||
() -> {
|
||||
(status) -> {
|
||||
if (status == TaskStatus.CANCEL_BY_MEMORY) {
|
||||
showHeapUsageBar();
|
||||
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
|
||||
return;
|
||||
}
|
||||
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
|
||||
initTree();
|
||||
update();
|
||||
@@ -581,9 +586,7 @@ public class MainWindow extends JFrame {
|
||||
decompilerArgs.setSkipResources(settings.isSkipResources());
|
||||
}
|
||||
settings.setLastSaveFilePath(fileChooser.getCurrentDirectory().toPath());
|
||||
ProgressMonitor progressMonitor = new ProgressMonitor(mainPanel, NLS.str("msg.saving_sources"), "", 0, 100);
|
||||
progressMonitor.setMillisToPopup(0);
|
||||
wrapper.saveAll(fileChooser.getSelectedFile(), progressMonitor);
|
||||
backgroundExecutor.execute(new ExportTask(this, wrapper, fileChooser.getSelectedFile()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import jadx.core.dex.nodes.VariableNode;
|
||||
import jadx.core.dex.visitors.RenameVisitor;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.TaskStatus;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JField;
|
||||
@@ -215,7 +216,11 @@ public class RenameDialog extends JDialog {
|
||||
if (!updatedTopClasses.isEmpty()) {
|
||||
mainWindow.getBackgroundExecutor().execute("Refreshing",
|
||||
Utils.collectionMap(updatedTopClasses, cls -> () -> refreshJClass(cls)),
|
||||
() -> {
|
||||
(status) -> {
|
||||
if (status == TaskStatus.CANCEL_BY_MEMORY) {
|
||||
mainWindow.showHeapUsageBar();
|
||||
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
|
||||
}
|
||||
if (node instanceof JPackage) {
|
||||
// reinit tree
|
||||
mainWindow.initTree();
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.ICodeCache;
|
||||
import jadx.api.ICodeInfo;
|
||||
|
||||
/**
|
||||
* Code cache with fixed size of wrapper code cache ('remove' and 'add' methods will do nothing).
|
||||
*/
|
||||
public class FixedCodeCache implements ICodeCache {
|
||||
|
||||
private final ICodeCache codeCache;
|
||||
|
||||
public FixedCodeCache(ICodeCache codeCache) {
|
||||
this.codeCache = codeCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ICodeInfo get(String clsFullName) {
|
||||
return this.codeCache.get(clsFullName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String clsFullName) {
|
||||
// no op
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(String clsFullName, ICodeInfo codeInfo) {
|
||||
// no op
|
||||
}
|
||||
}
|
||||
@@ -72,13 +72,7 @@ public class NLS {
|
||||
}
|
||||
|
||||
public static String str(String key, Object... parameters) {
|
||||
String value;
|
||||
try {
|
||||
value = localizedMessagesMap.getString(key);
|
||||
} catch (MissingResourceException e) {
|
||||
value = FALLBACK_MESSAGES_MAP.getString(key); // definitely exists
|
||||
}
|
||||
return String.format(value, parameters);
|
||||
return String.format(str(key), parameters);
|
||||
}
|
||||
|
||||
public static String str(String key, LangLocale locale) {
|
||||
|
||||
@@ -258,4 +258,9 @@ public class UiUtils {
|
||||
}
|
||||
return envVal;
|
||||
}
|
||||
|
||||
public static void errorMessage(Component parent, String message) {
|
||||
JOptionPane.showMessageDialog(parent, message,
|
||||
NLS.str("message.errorTitle"), JOptionPane.ERROR_MESSAGE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ nav.forward=Vorwärts
|
||||
|
||||
#message.taskTimeout=Task exceeded time limit of %d ms.
|
||||
#message.userCancelTask=Task was canceled by user.
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
message.indexingClassesSkipped=<html>Jadx hat nur noch wenig Speicherplatz. Daher wurden %d Klassen nicht indiziert.<br>Wenn Sie möchten, dass alle Klassen indiziert werden, Jadx mit erhöhter maximaler Heap-Größe neustarten.</html>
|
||||
|
||||
@@ -160,7 +165,7 @@ preferences.rename_printable=Ist druckbar
|
||||
#preferences.res_skip_file=
|
||||
|
||||
msg.open_file=Bitte Datei öffnen
|
||||
msg.saving_sources=Quellen speichern…
|
||||
msg.saving_sources=Quellen speichern
|
||||
msg.language_changed_title=Sprache speichern
|
||||
msg.language_changed=Neue Sprache wird beim nächsten Start der Anwendung angezeigt.
|
||||
msg.index_not_initialized=Index nicht initialisiert, Suche wird deaktiviert!
|
||||
|
||||
@@ -60,6 +60,11 @@ nav.forward=Forward
|
||||
|
||||
message.taskTimeout=Task exceeded time limit of %d ms.
|
||||
message.userCancelTask=Task was canceled by user.
|
||||
message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
message.taskError=Task failed with error (check log for details).
|
||||
message.errorTitle=Error
|
||||
|
||||
message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
message.indexingClassesSkipped=<html>Jadx is running low on memory. Therefore %d classes were not indexed.<br>If you want all classes to be indexed restart Jadx with increased maximum heap size.</html>
|
||||
|
||||
@@ -160,7 +165,7 @@ preferences.res_file_ext=File Extensions (e.g. .xml|.html), * means all
|
||||
preferences.res_skip_file=Skip files exceed (MB)
|
||||
|
||||
msg.open_file=Please open file
|
||||
msg.saving_sources=Saving sources...
|
||||
msg.saving_sources=Saving sources
|
||||
msg.language_changed_title=Language changed
|
||||
msg.language_changed=New language will be displayed the next time application starts.
|
||||
msg.index_not_initialized=Index not initialized, search will be disabled!
|
||||
|
||||
@@ -60,6 +60,11 @@ nav.forward=Adelante
|
||||
|
||||
#message.taskTimeout=Task exceeded time limit of %d ms.
|
||||
#message.userCancelTask=Task was canceled by user.
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
#message.indexingClassesSkipped=
|
||||
|
||||
@@ -160,7 +165,7 @@ preferences.reset_title=Reestablecer preferencias
|
||||
#preferences.res_skip_file=
|
||||
|
||||
msg.open_file=Por favor, abra un archivo
|
||||
msg.saving_sources=Guardando fuente...
|
||||
msg.saving_sources=Guardando fuente
|
||||
msg.language_changed_title=Idioma cambiado
|
||||
msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicación se inicie.
|
||||
msg.index_not_initialized=Índice no inicializado, ¡la bósqueda se desactivará!
|
||||
|
||||
@@ -60,6 +60,11 @@ nav.forward=앞으로
|
||||
|
||||
#message.taskTimeout=Task exceeded time limit of %d ms.
|
||||
#message.userCancelTask=Task was canceled by user.
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
message.indexingClassesSkipped=<html>Jadx의 메모리가 부족합니다. 따라서 %d 개의 클래스가 인덱싱되지 않았습니다. <br> 모든 클래스를 인덱싱하려면 최대 힙 크기를 늘린 상태로 Jadx를 다시 시작하십시오.</html>
|
||||
|
||||
@@ -160,7 +165,7 @@ preferences.res_file_ext=파일 확장자 (예: .xml|.html) (* 은 전체를 의
|
||||
preferences.res_skip_file=이 옵션보다 큰 파일 건너 뛰기 (MB)
|
||||
|
||||
msg.open_file=파일을 여십시오
|
||||
msg.saving_sources=소스 저장 중 ...
|
||||
msg.saving_sources=소스 저장 중
|
||||
msg.language_changed_title=언어 변경됨
|
||||
msg.language_changed=다음에 응용 프로그램이 시작되면 새 언어가 표시됩니다.
|
||||
msg.index_not_initialized=인덱스가 초기화되지 않았습니다. 검색이 비활성화됩니다!
|
||||
|
||||
@@ -60,6 +60,11 @@ nav.forward=前进
|
||||
|
||||
#message.taskTimeout=Task exceeded time limit of %d ms.
|
||||
#message.userCancelTask=Task was canceled by user.
|
||||
#message.memoryLow=Jadx is running low on memory. Please restart with increased maximum heap size.
|
||||
#message.taskError=Task failed with error (check log for details).
|
||||
#message.errorTitle=Error
|
||||
|
||||
#message.saveIncomplete=<html>Save incomplete.<br> %s<br> %d classes or resources were not saved!</html>
|
||||
#message.indexIncomplete=<html>Index of some classes skipped.<br> %s<br> %d classes were not indexed and will not appear in search results!</html>
|
||||
message.indexingClassesSkipped=<html>Jadx 的内存不足。因此,%d 类没有编入索引。<br>如果要将所有类编入索引,请使用增加的最大堆大小重新启动 Jadx。</html>
|
||||
|
||||
@@ -160,7 +165,7 @@ preferences.rename_printable=是可打印
|
||||
#preferences.res_skip_file=
|
||||
|
||||
msg.open_file=请打开文件
|
||||
msg.saving_sources=正在导出源代码...
|
||||
msg.saving_sources=正在导出源代码
|
||||
msg.language_changed_title=语言已更改
|
||||
msg.language_changed=在下次启动时将会显示新的语言。
|
||||
msg.index_not_initialized=索引尚未初始化,无法进行搜索!
|
||||
|
||||
Reference in New Issue
Block a user