fix(gui): add memory and time limits for decompile task (#1181)

This commit is contained in:
Skylot
2021-05-28 16:23:28 +01:00
parent 4bda3b9e9b
commit 9c252fb226
21 changed files with 332 additions and 376 deletions
@@ -6,8 +6,9 @@ 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 javax.swing.*;
import javax.swing.SwingWorker;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
@@ -15,6 +16,7 @@ import org.slf4j.LoggerFactory;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.ProgressPanel;
import jadx.gui.utils.UiUtils;
/**
* Class for run tasks in background with progress bar indication.
@@ -34,7 +36,7 @@ public class BackgroundExecutor {
this.taskQueueExecutor = makeTaskQueueExecutor();
}
public Future<Boolean> execute(IBackgroundTask task) {
public Future<TaskStatus> execute(IBackgroundTask task) {
TaskWorker taskWorker = new TaskWorker(task);
taskQueueExecutor.execute(() -> {
taskWorker.init();
@@ -46,7 +48,8 @@ public class BackgroundExecutor {
public void cancelAll() {
try {
taskQueueExecutor.shutdownNow();
taskQueueExecutor.awaitTermination(1, TimeUnit.SECONDS);
boolean complete = taskQueueExecutor.awaitTermination(2, TimeUnit.SECONDS);
LOG.debug("Background task executor terminated with status: {}", complete ? "complete" : "interrupted");
} catch (Exception e) {
LOG.error("Error terminating task executor", e);
} finally {
@@ -58,25 +61,18 @@ public class BackgroundExecutor {
execute(new SimpleTask(title, backgroundJobs, onFinishUiRunnable));
}
public void execute(String title, List<Runnable> backgroundJobs) {
execute(new SimpleTask(title, backgroundJobs, null));
}
public void execute(String title, Runnable backgroundRunnable, Runnable onFinishUiRunnable) {
execute(new SimpleTask(title, backgroundRunnable, onFinishUiRunnable));
}
public void execute(String title, Runnable backgroundRunnable) {
execute(new SimpleTask(title, backgroundRunnable, null));
}
private ThreadPoolExecutor makeTaskQueueExecutor() {
return (ThreadPoolExecutor) Executors.newFixedThreadPool(1);
}
private final class TaskWorker extends SwingWorker<Boolean, Void> {
private final class TaskWorker extends SwingWorker<TaskStatus, Void> {
private final IBackgroundTask task;
private long jobsCount;
private TaskStatus status = TaskStatus.WAIT;
public TaskWorker(IBackgroundTask task) {
this.task = task;
@@ -88,39 +84,24 @@ public class BackgroundExecutor {
}
@Override
protected Boolean doInBackground() throws Exception {
protected TaskStatus doInBackground() throws Exception {
progressPane.changeLabel(this, task.getTitle() + "");
progressPane.changeCancelBtnVisible(this, task.canBeCanceled());
progressPane.changeVisibility(this, true);
if (runJobs()) {
status = TaskStatus.COMPLETE;
}
return status;
}
private boolean runJobs() throws InterruptedException {
List<Runnable> jobs = task.scheduleJobs();
jobsCount = jobs.size();
LOG.debug("Starting background task '{}', jobs count: {}", task.getTitle(), jobsCount);
if (jobsCount == 1) {
jobs.get(0).run();
return true;
}
LOG.debug("Starting background task '{}', jobs count: {}, time limit: {} ms, memory check: {}",
task.getTitle(), jobsCount, task.timeLimit(), task.checkMemoryUsage());
status = TaskStatus.STARTED;
int threadsCount = mainWindow.getSettings().getThreadsCount();
if (threadsCount == 1) {
return runInCurrentThread(jobs);
}
return runInExecutor(jobs, threadsCount);
}
private boolean runInCurrentThread(List<Runnable> jobs) {
int k = 0;
for (Runnable job : jobs) {
job.run();
k++;
setProgress(calcProgress(k));
if (isCancelled()) {
return false;
}
}
return true;
}
private boolean runInExecutor(List<Runnable> jobs, int threadsCount) throws InterruptedException {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
for (Runnable job : jobs) {
executor.execute(job);
@@ -129,22 +110,74 @@ public class BackgroundExecutor {
return waitTermination(executor);
}
@SuppressWarnings("BusyWait")
private boolean waitTermination(ThreadPoolExecutor executor) throws InterruptedException {
while (true) {
if (executor.isTerminated()) {
BooleanSupplier cancelCheck = buildCancelCheck();
try {
while (true) {
if (executor.isTerminated()) {
return true;
}
if (cancelCheck.getAsBoolean()) {
performCancel(executor);
return false;
}
setProgress(calcProgress(executor.getCompletedTaskCount()));
Thread.sleep(1000);
}
} catch (InterruptedException e) {
LOG.debug("Task wait interrupted");
status = TaskStatus.CANCEL_BY_USER;
performCancel(executor);
return false;
} catch (Exception e) {
LOG.error("Task wait aborted by exception", e);
performCancel(executor);
return false;
}
}
private void performCancel(ThreadPoolExecutor executor) throws InterruptedException {
progressPane.changeLabel(this, task.getTitle() + " (Canceling)… ");
progressPane.changeIndeterminate(this, true);
// force termination
executor.shutdownNow();
boolean complete = executor.awaitTermination(5, TimeUnit.SECONDS);
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;
}
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;
}
if (isCancelled()) {
executor.shutdownNow();
progressPane.changeLabel(this, task.getTitle() + " (Canceling)… ");
progressPane.changeIndeterminate(this, true);
// force termination
executor.awaitTermination(5, TimeUnit.SECONDS);
return false;
if (checkMemoryUsage && !UiUtils.isFreeMemoryAvailable()) {
LOG.debug("Task '{}' memory limit reached, force cancel", task.getTitle());
status = TaskStatus.CANCEL_BY_MEMORY;
return true;
}
setProgress(calcProgress(executor.getCompletedTaskCount()));
Thread.sleep(500);
}
return simpleCancelCheck();
};
}
private int calcProgress(long done) {
@@ -154,7 +187,7 @@ public class BackgroundExecutor {
@Override
protected void done() {
progressPane.setVisible(false);
task.onFinish();
task.onFinish(status);
}
}
@@ -184,12 +217,7 @@ public class BackgroundExecutor {
}
@Override
public boolean canBeCanceled() {
return false;
}
@Override
public void onFinish() {
public void onFinish(TaskStatus status) {
if (onFinish != null) {
onFinish.run();
}
@@ -1,87 +0,0 @@
package jadx.gui.jobs;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.JadxWrapper;
public abstract class BackgroundJob {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundJob.class);
protected final JadxWrapper wrapper;
private final ThreadPoolExecutor executor;
private Future<Boolean> future;
public BackgroundJob(JadxWrapper wrapper, int threadsCount) {
this.wrapper = wrapper;
this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadsCount);
}
public synchronized Future<Boolean> process() {
if (future != null) {
return future;
}
ExecutorService shutdownExecutor = Executors.newSingleThreadExecutor();
FutureTask<Boolean> task = new ShutdownTask();
shutdownExecutor.execute(task);
shutdownExecutor.shutdown();
future = task;
return future;
}
private class ShutdownTask extends FutureTask<Boolean> {
public ShutdownTask() {
super(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
runJob();
executor.shutdown();
return executor.awaitTermination(5, TimeUnit.DAYS);
}
});
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
executor.shutdownNow();
return super.cancel(mayInterruptIfRunning);
}
}
protected abstract void runJob();
public abstract String getInfoString();
protected void addTask(Runnable runnable) {
executor.execute(runnable);
}
public void processAndWait() {
try {
process().get();
} catch (Exception e) {
LOG.error("BackgroundJob.processAndWait failed", e);
}
}
public synchronized boolean isComplete() {
try {
return future != null && future.isDone();
} catch (Exception e) {
LOG.error("BackgroundJob.isComplete failed", e);
return false;
}
}
public int getProgress() {
return (int) (executor.getCompletedTaskCount() * 100 / (double) executor.getTaskCount());
}
}
@@ -1,100 +0,0 @@
package jadx.gui.jobs;
import java.util.concurrent.Future;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.ui.ProgressPanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.TextSearchIndex;
/**
* Deprecated. Use {@link BackgroundExecutor} instead.
*/
public class BackgroundWorker extends SwingWorker<Void, Void> {
private static final Logger LOG = LoggerFactory.getLogger(BackgroundWorker.class);
private final CacheObject cache;
private final ProgressPanel progressPane;
public BackgroundWorker(CacheObject cacheObject, ProgressPanel progressPane) {
this.cache = cacheObject;
this.progressPane = progressPane;
}
public void exec() {
if (isDone()) {
return;
}
SwingUtilities.invokeLater(() -> progressPane.setVisible(true));
addPropertyChangeListener(progressPane);
execute();
}
public void stop() {
if (isDone()) {
return;
}
LOG.debug("Canceling background jobs ...");
cancel(false);
}
@Override
protected Void doInBackground() {
try {
System.gc();
LOG.debug("Memory usage: Before decompile: {}", UiUtils.memoryInfo());
runJob(cache.getDecompileJob());
LOG.debug("Memory usage: After decompile: {}", UiUtils.memoryInfo());
LOG.debug("Memory usage: Before index: {}", UiUtils.memoryInfo());
runJob(cache.getIndexJob());
LOG.debug("Memory usage: After index: {}", UiUtils.memoryInfo());
System.gc();
LOG.debug("Memory usage: After gc: {}", UiUtils.memoryInfo());
TextSearchIndex searchIndex = cache.getTextIndex();
if (searchIndex != null && searchIndex.getSkippedCount() > 0) {
LOG.warn("Indexing of some classes skipped, count: {}, low memory: {}",
searchIndex.getSkippedCount(), UiUtils.memoryInfo());
String msg = NLS.str("message.indexingClassesSkipped", searchIndex.getSkippedCount());
JOptionPane.showMessageDialog(null, msg);
}
} catch (Exception e) {
LOG.error("Exception in background worker", e);
}
return null;
}
private void runJob(BackgroundJob job) {
if (isCancelled() || job == null) {
return;
}
progressPane.changeLabel(this, job.getInfoString());
Future<Boolean> future = job.process();
while (!future.isDone()) {
try {
setProgress(job.getProgress());
if (isCancelled()) {
future.cancel(false);
}
Thread.sleep(500);
} catch (Exception e) {
LOG.error("Background worker error", e);
}
}
}
@Override
protected void done() {
progressPane.setVisible(false);
}
}
@@ -1,24 +0,0 @@
package jadx.gui.jobs;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
import jadx.gui.utils.NLS;
public class DecompileJob extends BackgroundJob {
public DecompileJob(JadxWrapper wrapper, int threadsCount) {
super(wrapper, threadsCount);
}
@Override
protected void runJob() {
for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(cls::decompile);
}
}
@Override
public String getInfoString() {
return NLS.str("progress.decompile") + "";
}
}
@@ -0,0 +1,113 @@
package jadx.gui.jobs;
import java.util.ArrayList;
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.JavaClass;
import jadx.gui.JadxWrapper;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
public class DecompileTask implements IBackgroundTask {
private static final Logger LOG = LoggerFactory.getLogger(DecompileTask.class);
private static final int CLS_LIMIT = Integer.parseInt(UiUtils.getEnvVar("JADX_CLS_PROCESS_LIMIT", "50"));
private final MainWindow mainWindow;
private final JadxWrapper wrapper;
private final AtomicInteger complete = new AtomicInteger(0);
private int expectedCompleteCount;
private long startTime;
public DecompileTask(MainWindow mainWindow, JadxWrapper wrapper) {
this.mainWindow = mainWindow;
this.wrapper = wrapper;
}
@Override
public String getTitle() {
return NLS.str("progress.decompile");
}
@Override
public List<Runnable> scheduleJobs() {
List<JavaClass> classes = wrapper.getIncludedClasses();
expectedCompleteCount = classes.size();
IndexService indexService = mainWindow.getCacheObject().getIndexService();
indexService.setComplete(false);
complete.set(0);
List<Runnable> jobs = new ArrayList<>(expectedCompleteCount + 1);
for (JavaClass cls : classes) {
jobs.add(() -> {
cls.decompile();
indexService.indexCls(cls);
complete.incrementAndGet();
});
}
jobs.add(indexService::indexResources);
startTime = System.currentTimeMillis();
return jobs;
}
@Override
public void onFinish(TaskStatus status) {
long taskTime = System.currentTimeMillis() - startTime;
long avgPerCls = taskTime / expectedCompleteCount;
LOG.info("Decompile task complete in {} ms (avg {} ms per class), classes: {},"
+ " time limit:{ total: {}ms, per cls: {}ms }, status: {}",
taskTime, avgPerCls, expectedCompleteCount, timeLimit(), CLS_LIMIT, status);
IndexService indexService = mainWindow.getCacheObject().getIndexService();
indexService.setComplete(true);
int complete = this.complete.get();
int skipped = expectedCompleteCount - complete;
if (skipped == 0) {
return;
}
LOG.warn("Decompile and indexing of some classes skipped: {}, status: {}", skipped, status);
switch (status) {
case CANCEL_BY_USER: {
String reason = NLS.str("message.userCancelTask");
String message = NLS.str("message.indexIncomplete", reason, skipped);
JOptionPane.showMessageDialog(mainWindow, message);
break;
}
case CANCEL_BY_TIMEOUT: {
String reason = NLS.str("message.taskTimeout", timeLimit());
String message = NLS.str("message.indexIncomplete", reason, skipped);
JOptionPane.showMessageDialog(mainWindow, message);
break;
}
case CANCEL_BY_MEMORY: {
mainWindow.showHeapUsageBar();
JOptionPane.showMessageDialog(mainWindow, NLS.str("message.indexingClassesSkipped", skipped));
break;
}
}
}
@Override
public boolean canBeCanceled() {
return true;
}
@Override
public int timeLimit() {
return expectedCompleteCount * CLS_LIMIT + 5000;
}
@Override
public boolean checkMemoryUsage() {
return true;
}
}
@@ -8,7 +8,23 @@ public interface IBackgroundTask {
List<Runnable> scheduleJobs();
void onFinish();
void onFinish(TaskStatus status);
boolean canBeCanceled();
default boolean canBeCanceled() {
return false;
}
/**
* Global (for all jobs) time limit in milliseconds (0 - to disable).
*/
default int timeLimit() {
return 0;
}
/**
* Executor will check memory usage on every tick and cancel job if no free memory available.
*/
default boolean checkMemoryUsage() {
return false;
}
}
@@ -8,36 +8,25 @@ import org.slf4j.LoggerFactory;
import jadx.api.ICodeWriter;
import jadx.api.JavaClass;
import jadx.gui.JadxWrapper;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.StringRef;
import jadx.gui.utils.search.TextSearchIndex;
public class IndexJob extends BackgroundJob {
public class IndexService {
private static final Logger LOG = LoggerFactory.getLogger(IndexJob.class);
private static final Logger LOG = LoggerFactory.getLogger(IndexService.class);
private final CacheObject cache;
private boolean indexComplete;
public IndexJob(JadxWrapper wrapper, CacheObject cache, int threadsCount) {
super(wrapper, threadsCount);
public IndexService(CacheObject cache) {
this.cache = cache;
}
@Override
protected void runJob() {
TextSearchIndex index = cache.getTextIndex();
addTask(index::indexResource);
for (final JavaClass cls : wrapper.getIncludedClasses()) {
addTask(() -> indexCls(cache, cls));
}
}
private static void indexCls(CacheObject cache, JavaClass cls) {
public void indexCls(JavaClass cls) {
try {
TextSearchIndex index = cache.getTextIndex();
CodeUsageInfo usageInfo = cache.getUsageInfo();
@@ -51,17 +40,18 @@ public class IndexJob extends BackgroundJob {
List<StringRef> lines = splitLines(cls);
usageInfo.processClass(cls, linesInfo, lines);
if (UiUtils.isFreeMemoryAvailable()) {
index.indexCode(cls, linesInfo, lines);
} else {
index.classCodeIndexSkipped(cls);
}
index.indexCode(cls, linesInfo, lines);
} catch (Exception e) {
LOG.error("Index error in class: {}", cls.getFullName(), e);
}
}
public static void refreshIndex(CacheObject cache, JavaClass cls) {
public void indexResources() {
TextSearchIndex index = cache.getTextIndex();
index.indexResource();
}
public void refreshIndex(JavaClass cls) {
TextSearchIndex index = cache.getTextIndex();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (index == null || usageInfo == null) {
@@ -69,7 +59,9 @@ public class IndexJob extends BackgroundJob {
}
index.remove(cls);
usageInfo.remove(cls);
indexCls(cache, cls);
if (UiUtils.isFreeMemoryAvailable()) {
indexCls(cls);
}
}
@NotNull
@@ -82,8 +74,11 @@ public class IndexJob extends BackgroundJob {
return lines;
}
@Override
public String getInfoString() {
return NLS.str("progress.index") + "";
public boolean isComplete() {
return indexComplete;
}
public void setComplete(boolean indexComplete) {
this.indexComplete = indexComplete;
}
}
@@ -0,0 +1,10 @@
package jadx.gui.jobs;
public enum TaskStatus {
WAIT,
STARTED,
COMPLETE,
CANCEL_BY_USER,
CANCEL_BY_TIMEOUT,
CANCEL_BY_MEMORY
}
@@ -46,9 +46,6 @@ import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.jobs.BackgroundJob;
import jadx.gui.jobs.BackgroundWorker;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.treemodel.JNode;
import jadx.gui.treemodel.JResSearchNode;
import jadx.gui.ui.codearea.AbstractCodeArea;
@@ -101,7 +98,7 @@ public abstract class CommonSearchDialog extends JDialog {
}
public void prepare() {
if (cache.getIndexJob().isComplete()) {
if (cache.getIndexService().isComplete()) {
loadFinishedCommon();
loadFinished();
return;
@@ -532,19 +529,8 @@ public abstract class CommonSearchDialog extends JDialog {
@Override
public Void doInBackground() {
try {
BackgroundWorker backgroundWorker = mainWindow.getBackgroundWorker();
if (backgroundWorker == null) {
return null;
}
backgroundWorker.exec();
DecompileJob decompileJob = cache.getDecompileJob();
progressPane.changeLabel(this, decompileJob.getInfoString());
decompileJob.processAndWait();
BackgroundJob indexJob = cache.getIndexJob();
progressPane.changeLabel(this, indexJob.getInfoString());
indexJob.processAndWait();
progressPane.changeLabel(this, NLS.str("progress.decompile") + ": ");
mainWindow.waitDecompileTask();
} catch (Exception e) {
LOG.error("Waiting background tasks failed", e);
}
@@ -553,11 +539,6 @@ public abstract class CommonSearchDialog extends JDialog {
@Override
public void done() {
try {
get();
} catch (Exception e) {
LOG.error("Load task failed", e);
}
loadFinishedCommon();
loadFinished();
}
@@ -35,6 +35,9 @@ import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import javax.swing.AbstractAction;
@@ -87,9 +90,9 @@ import jadx.core.utils.files.FileUtils;
import jadx.gui.JadxWrapper;
import jadx.gui.device.debugger.BreakpointManager;
import jadx.gui.jobs.BackgroundExecutor;
import jadx.gui.jobs.BackgroundWorker;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.jobs.DecompileTask;
import jadx.gui.jobs.IndexService;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.settings.JadxProject;
import jadx.gui.settings.JadxSettings;
import jadx.gui.settings.JadxSettingsWindow;
@@ -154,6 +157,7 @@ public class MainWindow extends JFrame {
private final transient JadxWrapper wrapper;
private final transient JadxSettings settings;
private final transient CacheObject cacheObject;
private final transient BackgroundExecutor backgroundExecutor;
private transient JadxProject project;
private transient Action newProjectAction;
private transient Action saveProjectAction;
@@ -177,8 +181,6 @@ public class MainWindow extends JFrame {
private transient Link updateLink;
private transient ProgressPanel progressPane;
private transient BackgroundWorker backgroundWorker;
private transient BackgroundExecutor backgroundExecutor;
private transient Theme editorTheme;
private JDebuggerPanel debuggerPanel;
@@ -196,10 +198,11 @@ public class MainWindow extends JFrame {
registerMouseNavigationButtons();
UiUtils.setWindowIcons(this);
loadSettings();
checkForUpdate();
newProject();
this.backgroundExecutor = new BackgroundExecutor(this);
checkForUpdate();
newProject();
}
public void init() {
@@ -393,7 +396,7 @@ public class MainWindow extends JFrame {
deobfToggleBtn.setSelected(settings.isDeobfuscationOn());
initTree();
update();
runBackgroundJobs();
runInitialBackgroundJobs();
BreakpointManager.init(paths.get(0).getParent());
onFinish.run();
});
@@ -469,37 +472,42 @@ public class MainWindow extends JFrame {
cacheObject.setJRoot(treeRoot);
cacheObject.setJadxSettings(settings);
int threadsCount = settings.getThreadsCount();
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount));
cacheObject.setIndexService(new IndexService(cacheObject));
cacheObject.setUsageInfo(new CodeUsageInfo(cacheObject.getNodeCache()));
cacheObject.setTextIndex(new TextSearchIndex(this));
}
synchronized void runBackgroundJobs() {
cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
synchronized void runInitialBackgroundJobs() {
if (settings.isAutoStartJobs()) {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
backgroundWorker.exec();
waitDecompileTask();
}
}, 1000);
}
}
public synchronized void cancelBackgroundJobs() {
if (backgroundExecutor != null) {
backgroundExecutor.cancelAll();
}
if (backgroundWorker != null) {
backgroundWorker.stop();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
resetCache();
private static final Object DECOMPILER_TASK_SYNC = new Object();
public void waitDecompileTask() {
synchronized (DECOMPILER_TASK_SYNC) {
try {
DecompileTask decompileTask = new DecompileTask(this, wrapper);
Future<TaskStatus> task = backgroundExecutor.execute(decompileTask);
task.get();
} catch (Exception e) {
LOG.error("Decompile task execution failed", e);
}
}
}
public void cancelBackgroundJobs() {
ExecutorService worker = Executors.newSingleThreadExecutor();
worker.execute(backgroundExecutor::cancelAll);
worker.shutdown();
}
public void reOpenFile() {
List<Path> openedFile = wrapper.getOpenPaths();
Map<String, Integer> openTabs = storeOpenTabs();
@@ -673,7 +681,10 @@ public class MainWindow extends JFrame {
} else if (obj instanceof QuarkReport) {
tabbedPane.showSimpleNode((JNode) obj);
} else if (obj instanceof JNode) {
tabbedPane.codeJump(new JumpPosition((JNode) obj));
JNode node = (JNode) obj;
if (node.getRootClass() != null) {
tabbedPane.codeJump(new JumpPosition(node));
}
}
} catch (Exception e) {
LOG.error("Content loading error", e);
@@ -1283,10 +1294,6 @@ public class MainWindow extends JFrame {
return cacheObject;
}
public BackgroundWorker getBackgroundWorker() {
return backgroundWorker;
}
public BackgroundExecutor getBackgroundExecutor() {
return backgroundExecutor;
}
@@ -1314,6 +1321,11 @@ public class MainWindow extends JFrame {
debuggerPanel = null;
}
public void showHeapUsageBar() {
settings.setShowHeapUsageBar(true);
heapUsageBar.setVisible(true);
}
private void initDebuggerPanel() {
if (debuggerPanel == null) {
debuggerPanel = new JDebuggerPanel(this);
@@ -41,7 +41,6 @@ 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.IndexJob;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
@@ -244,7 +243,7 @@ public class RenameDialog extends JDialog {
private void refreshJClass(JClass cls) {
try {
cls.reload();
IndexJob.refreshIndex(cache, cls.getCls());
cache.getIndexService().refreshIndex(cls.getCls());
} catch (Exception e) {
LOG.error("Failed to reload class: {}", cls.getFullName(), e);
}
@@ -153,7 +153,7 @@ public class SearchDialog extends CommonSearchDialog {
}
private TextSearchIndex checkIndex() {
if (!cache.getIndexJob().isComplete()) {
if (!cache.getIndexService().isComplete()) {
if (isFullIndexNeeded()) {
prepare();
}
@@ -18,7 +18,6 @@ import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.JadxDecompiler;
import jadx.api.JavaNode;
import jadx.gui.jobs.IndexJob;
import jadx.gui.settings.JadxProject;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
@@ -222,7 +221,7 @@ public final class CodeArea extends AbstractCodeArea {
caretFix.save();
cls.reload();
IndexJob.refreshIndex(getMainWindow().getCacheObject(), cls.getCls());
getMainWindow().getCacheObject().getIndexService().refreshIndex(cls.getCls());
ClassCodeContentPanel codeContentPanel = (ClassCodeContentPanel) this.contentPanel;
codeContentPanel.getTabbedPane().refresh(cls);
@@ -6,8 +6,7 @@ import java.util.Set;
import org.jetbrains.annotations.Nullable;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.jobs.IndexService;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.SearchDialog;
@@ -16,8 +15,7 @@ import jadx.gui.utils.search.TextSearchIndex;
public class CacheObject {
private DecompileJob decompileJob;
private IndexJob indexJob;
private IndexService indexService;
private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo;
@@ -36,8 +34,7 @@ public class CacheObject {
public void reset() {
jRoot = null;
settings = null;
decompileJob = null;
indexJob = null;
indexService = null;
textIndex = null;
lastSearch = null;
jNodeCache = new JNodeCache();
@@ -45,14 +42,6 @@ public class CacheObject {
lastSearchOptions = new HashMap<>();
}
public DecompileJob getDecompileJob() {
return decompileJob;
}
public void setDecompileJob(DecompileJob decompileJob) {
this.decompileJob = decompileJob;
}
public TextSearchIndex getTextIndex() {
return textIndex;
}
@@ -87,12 +76,12 @@ public class CacheObject {
this.commentsIndex = commentsIndex;
}
public IndexJob getIndexJob() {
return indexJob;
public IndexService getIndexService() {
return indexService;
}
public void setIndexJob(IndexJob indexJob) {
this.indexJob = indexJob;
public void setIndexService(IndexService indexService) {
this.indexService = indexService;
}
public JNodeCache getNodeCache() {
@@ -1,5 +1,7 @@
package jadx.gui.utils;
import java.util.Objects;
import jadx.api.CodePosition;
import jadx.api.JavaNode;
import jadx.gui.treemodel.JNode;
@@ -10,11 +12,11 @@ public class JumpPosition {
private int pos;
public JumpPosition(JNode jumpNode) {
this(jumpNode.getRootClass(), jumpNode.getLine(), jumpNode.getPos());
this(Objects.requireNonNull(jumpNode.getRootClass()), jumpNode.getLine(), jumpNode.getPos());
}
public JumpPosition(JNode jumpNode, CodePosition codePos) {
this(jumpNode.getRootClass(), codePos.getLine(), codePos.getPos());
this(Objects.requireNonNull(jumpNode.getRootClass()), codePos.getLine(), codePos.getPos());
}
public JumpPosition(JNode node, int line, int pos) {
@@ -250,4 +250,12 @@ public class UiUtils {
SwingUtilities.convertPointFromScreen(pos, comp);
return pos;
}
public static String getEnvVar(String varName, String defValue) {
String envVal = System.getenv(varName);
if (envVal == null) {
return defValue;
}
return envVal;
}
}
@@ -36,7 +36,7 @@ tree.loading=Laden…
progress.load=Laden
progress.decompile=Dekompilieren
progress.index=Indizieren
#progress.index=Indizieren
error_dialog.title=Fehler
@@ -58,6 +58,9 @@ tabs.smali=Smali
nav.back=Zurück
nav.forward=Vorwärts
#message.taskTimeout=Task exceeded time limit of %d ms.
#message.userCancelTask=Task was canceled by user.
#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>
heapUsage.text=JADX-Speicherauslastung: %.2f GB von %.2f GB
@@ -36,7 +36,7 @@ tree.loading=Loading...
progress.load=Loading
progress.decompile=Decompiling
progress.index=Indexing
#progress.index=Indexing
error_dialog.title=Error
@@ -58,6 +58,9 @@ tabs.smali=Smali
nav.back=Back
nav.forward=Forward
message.taskTimeout=Task exceeded time limit of %d ms.
message.userCancelTask=Task was canceled by user.
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>
heapUsage.text=JADX memory usage: %.2f GB of %.2f GB
@@ -36,7 +36,7 @@ tree.loading=Cargando...
progress.load=Cargando
progress.decompile=Decompiling
progress.index=Indexing
#progress.index=Indexing
#error_dialog.title=
@@ -58,6 +58,9 @@ tabs.closeAll=Cerrar todo
nav.back=Atrás
nav.forward=Adelante
#message.taskTimeout=Task exceeded time limit of %d ms.
#message.userCancelTask=Task was canceled by user.
#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=
#heapUsage.text=
@@ -36,7 +36,7 @@ tree.loading=로딩중...
progress.load=로딩중
progress.decompile=디컴파일 중
progress.index=인덱싱 중
#progress.index=인덱싱 중
error_dialog.title=오류
@@ -58,6 +58,9 @@ tabs.smali=Smali
nav.back=뒤로
nav.forward=앞으로
#message.taskTimeout=Task exceeded time limit of %d ms.
#message.userCancelTask=Task was canceled by user.
#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>
heapUsage.text=JADX 메모리 사용량 : %.2f GB / %.2f GB
@@ -36,7 +36,7 @@ tree.loading=稍等...
progress.load=稍等
progress.decompile=Decompiling
progress.index=Indexing
#progress.index=Indexing
error_dialog.title=错误
@@ -58,6 +58,9 @@ tabs.code=代码
nav.back=后退
nav.forward=前进
#message.taskTimeout=Task exceeded time limit of %d ms.
#message.userCancelTask=Task was canceled by user.
#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>
heapUsage.text=JADX 内存使用率:%.2f GB 共 %.2f GB