提交 1c08d854 编写于 作者: S Skylot

fix(gui): add memory limit checks to export and load tasks (#1181)

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