Commit 43592c3e authored by Skylot's avatar Skylot

gui: improve memory usage (#79)

- don't use suffix tree in search
- decrease default working threads count (only 1 for background jobs)
- use string refs for store only one code string without duplicates
- use cache for creating UI nodes
- allow to disable autostart for background jobs (decompilation and index)
parent b46093b3
...@@ -29,7 +29,7 @@ public class JadxCLIArgs implements IJadxArgs { ...@@ -29,7 +29,7 @@ public class JadxCLIArgs implements IJadxArgs {
protected String outDirName; protected String outDirName;
@Parameter(names = {"-j", "--threads-count"}, description = "processing threads count") @Parameter(names = {"-j", "--threads-count"}, description = "processing threads count")
protected int threadsCount = Runtime.getRuntime().availableProcessors(); protected int threadsCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
@Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)") @Parameter(names = {"-f", "--fallback"}, description = "make simple dump (using goto instead of 'if', 'for', etc)")
protected boolean fallbackMode = false; protected boolean fallbackMode = false;
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
<level>INFO</level> <level>INFO</level>
</filter> </filter>
<encoder> <encoder>
<pattern>%-5level - %msg%n</pattern> <pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
......
...@@ -2,7 +2,7 @@ package jadx.gui.jobs; ...@@ -2,7 +2,7 @@ package jadx.gui.jobs;
import jadx.gui.ui.ProgressPanel; import jadx.gui.ui.ProgressPanel;
import jadx.gui.utils.CacheObject; import jadx.gui.utils.CacheObject;
import jadx.gui.utils.TextSearchIndex; import jadx.gui.utils.search.TextSearchIndex;
import jadx.gui.utils.Utils; import jadx.gui.utils.Utils;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
......
...@@ -3,12 +3,15 @@ package jadx.gui.jobs; ...@@ -3,12 +3,15 @@ package jadx.gui.jobs;
import jadx.api.JavaClass; import jadx.api.JavaClass;
import jadx.core.codegen.CodeWriter; import jadx.core.codegen.CodeWriter;
import jadx.gui.JadxWrapper; import jadx.gui.JadxWrapper;
import jadx.gui.settings.JadxSettings;
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.TextSearchIndex; import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.Utils; import jadx.gui.utils.Utils;
import jadx.gui.utils.search.StringRef;
import jadx.gui.utils.search.TextSearchIndex;
import java.util.List;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
...@@ -20,15 +23,16 @@ public class IndexJob extends BackgroundJob { ...@@ -20,15 +23,16 @@ public class IndexJob extends BackgroundJob {
private final CacheObject cache; private final CacheObject cache;
private final boolean useFastSearch; private final boolean useFastSearch;
public IndexJob(JadxWrapper wrapper, JadxSettings settings, CacheObject cache) { public IndexJob(JadxWrapper wrapper, CacheObject cache, int threadsCount, boolean useFastSearch) {
super(wrapper, settings.getThreadsCount()); super(wrapper, threadsCount);
this.useFastSearch = settings.isUseFastSearch(); this.useFastSearch = useFastSearch;
this.cache = cache; this.cache = cache;
} }
protected void runJob() { protected void runJob() {
final TextSearchIndex index = new TextSearchIndex(); JNodeCache nodeCache = cache.getNodeCache();
final CodeUsageInfo usageInfo = new CodeUsageInfo(); final TextSearchIndex index = new TextSearchIndex(nodeCache, useFastSearch);
final CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
cache.setTextIndex(index); cache.setTextIndex(index);
cache.setUsageInfo(usageInfo); cache.setUsageInfo(usageInfo);
for (final JavaClass cls : wrapper.getClasses()) { for (final JavaClass cls : wrapper.getClasses()) {
...@@ -39,10 +43,10 @@ public class IndexJob extends BackgroundJob { ...@@ -39,10 +43,10 @@ public class IndexJob extends BackgroundJob {
index.indexNames(cls); index.indexNames(cls);
CodeLinesInfo linesInfo = new CodeLinesInfo(cls); CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
String[] lines = splitIntoLines(cls); List<StringRef> lines = splitLines(cls);
usageInfo.processClass(cls, linesInfo, lines); usageInfo.processClass(cls, linesInfo, lines);
if (useFastSearch && Utils.isFreeMemoryAvailable()) { if (Utils.isFreeMemoryAvailable()) {
index.indexCode(cls, linesInfo, lines); index.indexCode(cls, linesInfo, lines);
} else { } else {
index.classCodeIndexSkipped(cls); index.classCodeIndexSkipped(cls);
...@@ -56,11 +60,11 @@ public class IndexJob extends BackgroundJob { ...@@ -56,11 +60,11 @@ public class IndexJob extends BackgroundJob {
} }
@NotNull @NotNull
protected String[] splitIntoLines(JavaClass cls) { protected List<StringRef> splitLines(JavaClass cls) {
String[] lines = cls.getCode().split(CodeWriter.NL); List<StringRef> lines = StringRef.split(cls.getCode(), CodeWriter.NL);
int count = lines.length; int size = lines.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < size; i++) {
lines[i] = lines[i].trim(); lines.set(i, lines.get(i).trim());
} }
return lines; return lines;
} }
......
...@@ -29,6 +29,11 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -29,6 +29,11 @@ public class JadxSettings extends JadxCLIArgs {
private List<String> recentFiles = new ArrayList<String>(); private List<String> recentFiles = new ArrayList<String>();
private String fontStr = ""; private String fontStr = "";
private boolean useFastSearch = false; private boolean useFastSearch = false;
private boolean autoStartJobs = true;
public JadxSettings() {
setSkipResources(true);
}
public void sync() { public void sync() {
JadxSettingsAdapter.store(this); JadxSettingsAdapter.store(this);
...@@ -137,13 +142,22 @@ public class JadxSettings extends JadxCLIArgs { ...@@ -137,13 +142,22 @@ public class JadxSettings extends JadxCLIArgs {
} }
public boolean isUseFastSearch() { public boolean isUseFastSearch() {
return useFastSearch; return false;
// return useFastSearch;
} }
public void setUseFastSearch(boolean useFastSearch) { public void setUseFastSearch(boolean useFastSearch) {
this.useFastSearch = useFastSearch; this.useFastSearch = useFastSearch;
} }
public boolean isAutoStartJobs() {
return autoStartJobs;
}
public void setAutoStartJobs(boolean autoStartJobs) {
this.autoStartJobs = autoStartJobs;
}
public Font getFont() { public Font getFont() {
if (fontStr.isEmpty()) { if (fontStr.isEmpty()) {
return DEFAULT_FONT; return DEFAULT_FONT;
......
...@@ -24,7 +24,6 @@ import java.awt.Dimension; ...@@ -24,7 +24,6 @@ import java.awt.Dimension;
import java.awt.Font; import java.awt.Font;
import java.awt.GridBagConstraints; import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets; import java.awt.Insets;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
...@@ -57,7 +56,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -57,7 +56,7 @@ public class JadxSettingsWindow extends JDialog {
private void initUI() { private void initUI() {
JPanel panel = new JPanel(); JPanel panel = new JPanel();
panel.setLayout(new GridLayout(0, 1, 10, 5)); panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(makeDeobfuscationGroup()); panel.add(makeDeobfuscationGroup());
panel.add(makeOtherGroup()); panel.add(makeOtherGroup());
...@@ -102,7 +101,6 @@ public class JadxSettingsWindow extends JDialog { ...@@ -102,7 +101,6 @@ public class JadxSettingsWindow extends JDialog {
} }
private SettingsGroup makeDeobfuscationGroup() { private SettingsGroup makeDeobfuscationGroup() {
JCheckBox deobfOn = new JCheckBox(); JCheckBox deobfOn = new JCheckBox();
deobfOn.setSelected(settings.isDeobfuscationOn()); deobfOn.setSelected(settings.isDeobfuscationOn());
deobfOn.addItemListener(new ItemListener() { deobfOn.addItemListener(new ItemListener() {
...@@ -239,7 +237,16 @@ public class JadxSettingsWindow extends JDialog { ...@@ -239,7 +237,16 @@ public class JadxSettingsWindow extends JDialog {
} }
}); });
JCheckBox autoStartJobs = new JCheckBox();
autoStartJobs.setSelected(settings.isAutoStartJobs());
autoStartJobs.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
settings.setAutoStartJobs(e.getStateChange() == ItemEvent.SELECTED);
}
});
JCheckBox fastSearch = new JCheckBox(); JCheckBox fastSearch = new JCheckBox();
fastSearch.setEnabled(false);
fastSearch.setSelected(settings.isUseFastSearch()); fastSearch.setSelected(settings.isUseFastSearch());
fastSearch.addItemListener(new ItemListener() { fastSearch.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) { public void itemStateChanged(ItemEvent e) {
...@@ -257,6 +264,7 @@ public class JadxSettingsWindow extends JDialog { ...@@ -257,6 +264,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.raw_cfg"), rawCfg); other.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
other.addRow(NLS.str("preferences.font"), fontBtn); other.addRow(NLS.str("preferences.font"), fontBtn);
other.addRow(NLS.str("preferences.fast_search"), fastSearch); other.addRow(NLS.str("preferences.fast_search"), fastSearch);
other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
return other; return other;
} }
......
package jadx.gui.treemodel; package jadx.gui.treemodel;
import jadx.api.JavaNode; import jadx.api.JavaNode;
import jadx.gui.utils.search.StringRef;
import javax.swing.Icon; import javax.swing.Icon;
...@@ -10,12 +11,12 @@ public class CodeNode extends JNode { ...@@ -10,12 +11,12 @@ public class CodeNode extends JNode {
private final JNode jNode; private final JNode jNode;
private final JClass jParent; private final JClass jParent;
private final String line; private final StringRef line;
private final int lineNum; private final int lineNum;
public CodeNode(JavaNode javaNode, int lineNum, String line) { public CodeNode(JNode jNode, int lineNum, StringRef line) {
this.jNode = makeFrom(javaNode); this.jNode = jNode;
this.jParent = jNode.getJParent(); this.jParent = this.jNode.getJParent();
this.line = line; this.line = line;
this.lineNum = lineNum; this.lineNum = lineNum;
} }
...@@ -54,7 +55,7 @@ public class CodeNode extends JNode { ...@@ -54,7 +55,7 @@ public class CodeNode extends JNode {
@Override @Override
public String makeDescString() { public String makeDescString() {
return line; return line.toString();
} }
@Override @Override
......
package jadx.gui.treemodel; package jadx.gui.treemodel;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode; import jadx.api.JavaNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultMutableTreeNode;
...@@ -12,24 +8,6 @@ import javax.swing.tree.DefaultMutableTreeNode; ...@@ -12,24 +8,6 @@ import javax.swing.tree.DefaultMutableTreeNode;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
public abstract class JNode extends DefaultMutableTreeNode { public abstract class JNode extends DefaultMutableTreeNode {
public static JNode makeFrom(JavaNode node) {
if (node == null) {
return null;
}
if (node instanceof JavaClass) {
JClass p = (JClass) makeFrom(node.getDeclaringClass());
return new JClass((JavaClass) node, p);
}
if (node instanceof JavaMethod) {
JavaMethod mth = (JavaMethod) node;
return new JMethod(mth, (JClass) makeFrom(mth.getDeclaringClass()));
}
if (node instanceof JavaField) {
JavaField fld = (JavaField) node;
return new JField(fld, (JClass) makeFrom(fld.getDeclaringClass()));
}
throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass());
}
public abstract JClass getJParent(); public abstract JClass getJParent();
......
...@@ -7,7 +7,7 @@ import jadx.gui.treemodel.TextNode; ...@@ -7,7 +7,7 @@ import jadx.gui.treemodel.TextNode;
import jadx.gui.utils.CacheObject; import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.Position; import jadx.gui.utils.Position;
import jadx.gui.utils.TextSearchIndex; import jadx.gui.utils.search.TextSearchIndex;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
import javax.swing.Box; import javax.swing.Box;
...@@ -56,7 +56,7 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -56,7 +56,7 @@ public abstract class CommonSearchDialog extends JDialog {
private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class); private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class);
private static final long serialVersionUID = 8939332306115370276L; private static final long serialVersionUID = 8939332306115370276L;
public static final int MAX_RESULTS_COUNT = 1000; public static final int MAX_RESULTS_COUNT = 100;
protected final TabbedPane tabbedPane; protected final TabbedPane tabbedPane;
protected final CacheObject cache; protected final CacheObject cache;
...@@ -328,7 +328,10 @@ public abstract class CommonSearchDialog extends JDialog { ...@@ -328,7 +328,10 @@ public abstract class CommonSearchDialog extends JDialog {
textArea.setRows(1); textArea.setRows(1);
textArea.setColumns(textArea.getText().length()); textArea.setColumns(textArea.getText().length());
if (highlightText != null) { if (highlightText != null) {
SearchEngine.markAll(textArea, new SearchContext(highlightText)); SearchContext searchContext = new SearchContext(highlightText);
searchContext.setMatchCase(true);
searchContext.setMarkAll(true);
SearchEngine.markAll(textArea, searchContext);
} }
return textArea; return textArea;
} }
......
...@@ -214,7 +214,8 @@ class ContentArea extends RSyntaxTextArea { ...@@ -214,7 +214,8 @@ class ContentArea extends RSyntaxTextArea {
return; return;
} }
MainWindow mainWindow = contentPanel.getTabbedPane().getMainWindow(); MainWindow mainWindow = contentPanel.getTabbedPane().getMainWindow();
UsageDialog usageDialog = new UsageDialog(mainWindow, JNode.makeFrom(node)); JNode jNode = mainWindow.getCacheObject().getNodeCache().makeFrom(node);
UsageDialog usageDialog = new UsageDialog(mainWindow, jNode);
usageDialog.setVisible(true); usageDialog.setVisible(true);
} }
......
...@@ -194,20 +194,23 @@ public class MainWindow extends JFrame { ...@@ -194,20 +194,23 @@ public class MainWindow extends JFrame {
protected void resetCache() { protected void resetCache() {
cacheObject.reset(); cacheObject.reset();
int threadsCount = settings.getThreadsCount(); // TODO: decompilation freezes sometime with several threads
int threadsCount = 1; // settings.getThreadsCount();
cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount)); cacheObject.setDecompileJob(new DecompileJob(wrapper, threadsCount));
cacheObject.setIndexJob(new IndexJob(wrapper, settings, cacheObject)); cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount, settings.isUseFastSearch()));
} }
private synchronized void runBackgroundJobs() { private synchronized void runBackgroundJobs() {
cancelBackgroundJobs(); cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane); backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
new Timer().schedule(new TimerTask() { if (settings.isAutoStartJobs()) {
@Override new Timer().schedule(new TimerTask() {
public void run() { @Override
backgroundWorker.exec(); public void run() {
} backgroundWorker.exec();
}, 1000); }
}, 1000);
}
} }
public synchronized void cancelBackgroundJobs() { public synchronized void cancelBackgroundJobs() {
......
package jadx.gui.ui; package jadx.gui.ui;
import jadx.gui.utils.NLS; import jadx.gui.utils.NLS;
import jadx.gui.utils.TextSearchIndex; import jadx.gui.utils.search.TextSearchIndex;
import jadx.gui.utils.TextStandardActions; import jadx.gui.utils.TextStandardActions;
import javax.swing.BorderFactory; import javax.swing.BorderFactory;
......
...@@ -2,6 +2,7 @@ package jadx.gui.utils; ...@@ -2,6 +2,7 @@ package jadx.gui.utils;
import jadx.gui.jobs.DecompileJob; import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob; import jadx.gui.jobs.IndexJob;
import jadx.gui.utils.search.TextSearchIndex;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
...@@ -13,10 +14,12 @@ public class CacheObject { ...@@ -13,10 +14,12 @@ public class CacheObject {
private TextSearchIndex textIndex; private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo; private CodeUsageInfo usageInfo;
private String lastSearch; private String lastSearch;
private JNodeCache jNodeCache = new JNodeCache();
public void reset() { public void reset() {
textIndex = null; textIndex = null;
lastSearch = null; lastSearch = null;
jNodeCache = new JNodeCache();
usageInfo = null; usageInfo = null;
} }
...@@ -62,4 +65,8 @@ public class CacheObject { ...@@ -62,4 +65,8 @@ public class CacheObject {
public void setIndexJob(IndexJob indexJob) { public void setIndexJob(IndexJob indexJob) {
this.indexJob = indexJob; this.indexJob = indexJob;
} }
public JNodeCache getNodeCache() {
return jNodeCache;
}
} }
...@@ -5,6 +5,7 @@ import jadx.api.JavaClass; ...@@ -5,6 +5,7 @@ import jadx.api.JavaClass;
import jadx.api.JavaNode; import jadx.api.JavaNode;
import jadx.gui.treemodel.CodeNode; import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.utils.search.StringRef;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
...@@ -22,19 +23,25 @@ public class CodeUsageInfo { ...@@ -22,19 +23,25 @@ public class CodeUsageInfo {
} }
} }
private final JNodeCache nodeCache;
public CodeUsageInfo(JNodeCache nodeCache) {
this.nodeCache = nodeCache;
}
private final Map<JNode, UsageInfo> usageMap = new HashMap<JNode, UsageInfo>(); private final Map<JNode, UsageInfo> usageMap = new HashMap<JNode, UsageInfo>();
public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, String[] lines) { public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, List<StringRef> lines) {
Map<CodePosition, JavaNode> usage = javaClass.getUsageMap(); Map<CodePosition, JavaNode> usage = javaClass.getUsageMap();
for (Map.Entry<CodePosition, JavaNode> entry : usage.entrySet()) { for (Map.Entry<CodePosition, JavaNode> entry : usage.entrySet()) {
CodePosition codePosition = entry.getKey(); CodePosition codePosition = entry.getKey();
JavaNode javaNode = entry.getValue(); JavaNode javaNode = entry.getValue();
addUsage(JNode.makeFrom(javaNode), javaClass, linesInfo, codePosition, lines); addUsage(nodeCache.makeFrom(javaNode), javaClass, linesInfo, codePosition, lines);
} }
} }
private void addUsage(JNode jNode, JavaClass javaClass, private void addUsage(JNode jNode, JavaClass javaClass,
CodeLinesInfo linesInfo, CodePosition codePosition, String[] lines) { CodeLinesInfo linesInfo, CodePosition codePosition, List<StringRef> lines) {
UsageInfo usageInfo = usageMap.get(jNode); UsageInfo usageInfo = usageMap.get(jNode);
if (usageInfo == null) { if (usageInfo == null) {
usageInfo = new UsageInfo(); usageInfo = new UsageInfo();
...@@ -42,8 +49,9 @@ public class CodeUsageInfo { ...@@ -42,8 +49,9 @@ public class CodeUsageInfo {
} }
int line = codePosition.getLine(); int line = codePosition.getLine();
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line); JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
String codeLine = lines[line - 1].trim(); StringRef codeLine = lines.get(line - 1);
CodeNode codeNode = new CodeNode(javaNodeByLine == null ? javaClass : javaNodeByLine, line, codeLine); JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
CodeNode codeNode = new CodeNode(node, line, codeLine);
usageInfo.getUsageList().add(codeNode); usageInfo.getUsageList().add(codeNode);
} }
......
package jadx.gui.utils;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JField;
import jadx.gui.treemodel.JMethod;
import jadx.gui.treemodel.JNode;
import java.util.HashMap;
import java.util.Map;
public class JNodeCache {
private final Map<JavaNode, JNode> cache = new HashMap<JavaNode, JNode>();
public JNode makeFrom(JavaNode javaNode) {
if (javaNode == null) {
return null;
}
JNode jNode = cache.get(javaNode);
if (jNode == null) {
jNode = convert(javaNode);
cache.put(javaNode, jNode);
}
return jNode;
}
private JNode convert(JavaNode node) {
if (node == null) {
return null;
}
if (node instanceof JavaClass) {
JClass p = (JClass) makeFrom(node.getDeclaringClass());
return new JClass((JavaClass) node, p);
}
if (node instanceof JavaMethod) {
JavaMethod mth = (JavaMethod) node;
return new JMethod(mth, (JClass) makeFrom(mth.getDeclaringClass()));
}
if (node instanceof JavaField) {
JavaField fld = (JavaField) node;
return new JField(fld, (JClass) makeFrom(fld.getDeclaringClass()));
}
throw new JadxRuntimeException("Unknown type for JavaNode: " + node.getClass());
}
}
...@@ -99,10 +99,11 @@ public class Utils { ...@@ -99,10 +99,11 @@ public class Utils {
long allocatedMemory = runtime.totalMemory(); long allocatedMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory(); long freeMemory = runtime.freeMemory();
sb.append("free: ").append(format(freeMemory)); sb.append("heap: ").append(format(allocatedMemory - freeMemory));
sb.append(", allocated: ").append(format(allocatedMemory)); sb.append(", allocated: ").append(format(allocatedMemory));
sb.append(", max: ").append(format(maxMemory)); sb.append(", free: ").append(format(freeMemory));
sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory)); sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory));
sb.append(", max: ").append(format(maxMemory));
return sb.toString(); return sb.toString();
} }
......
package jadx.gui.utils.search;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class CodeIndex<T> extends SearchIndex<T> {
private final List<StringRef> keys = new ArrayList<StringRef>();
private final List<T> values = new ArrayList<T>();
@Override
public void put(String str, T value) {
throw new UnsupportedOperationException("CodeIndex.put for string not supported");
}
@Override
public void put(StringRef str, T value) {
if (str == null || str.length() == 0) {
return;
}
keys.add(str);
values.add(value);
}
@Override
public boolean isStringRefSupported() {
return true;
}
@Override
public List<T> getValuesForKeysContaining(String str) {
int size = size();
if (size == 0) {
return Collections.emptyList();
}
List<T> results = new ArrayList<T>();
for (int i = 0; i < size; i++) {
StringRef key = keys.get(i);
if (key.indexOf(str) != -1) {
results.add(values.get(i));
}
}
return results;
}
@Override
public int size() {
return keys.size();
}
}
package jadx.gui.utils.search;
import java.util.List;
public abstract class SearchIndex<V> {
public abstract void put(String str, V value);
public void put(StringRef str, V value) {
throw new UnsupportedOperationException("StringRef put not supported");
}
public boolean isStringRefSupported() {
return false;
}
public abstract List<V> getValuesForKeysContaining(String str);
public abstract int size();
}
package jadx.gui.utils.search;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SimpleIndex<T> extends SearchIndex<T> {
private final List<String> keys = new ArrayList<String>();
private final List<T> values = new ArrayList<T>();
@Override
public void put(String str, T value) {
keys.add(str);
values.add(value);
}
@Override
public List<T> getValuesForKeysContaining(String str) {
int size = size();
if (size == 0) {
return Collections.emptyList();
}
List<T> results = new ArrayList<T>();
for (int i = 0; i < size; i++) {
String key = keys.get(i);
if (key.contains(str)) {
results.add(values.get(i));
}
}
return results;
}
@Override
public int size() {
return keys.size();
}
}
package jadx.gui.utils.search;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.NotNull;
public class StringRef implements CharSequence {
private final String refStr;
private final int offset;
private final int length;
private int hash;
public static StringRef subString(String str, int from, int to) {
return new StringRef(str, from, to - from);
}
public static StringRef subString(String str, int from) {
return subString(str, from, str.length());
}
public static StringRef fromStr(String str) {
return new StringRef(str, 0, str.length());
}
private StringRef(String str, int from, int length) {
this.refStr = str;
this.offset = from;
this.length = length;
}
@Override
public int length() {
return length;
}
@Override
public char charAt(int index) {
return refStr.charAt(offset + index);
}
@Override
public CharSequence subSequence(int start, int end) {
return subString(refStr, start, end);
}
public StringRef trim() {
int start = offset;
int end = start + length;
String str = refStr;
while ((start < end) && (str.charAt(start) <= ' ')) {
start++;
}
while ((start < end) && (str.charAt(end - 1) <= ' ')) {
end--;
}
if ((start > offset) || (end < offset + length)) {
return subString(str, start, end);
}
return this;
}
public int indexOf(String str) {
return indexOf(str, 0);
}
public int indexOf(String str, int from) {
return indexOf(refStr, offset, length, str, 0, str.length(), from);
}
private static int indexOf(String source, int sourceOffset, int sourceCount,
String target, int targetOffset, int targetCount,
int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return -1;
}
char first = target.charAt(targetOffset);
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
if (source.charAt(i) != first) {
while (++i <= max && source.charAt(i) != first) {
}
}
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
int k = targetOffset + 1;
while (j < end && source.charAt(j) == target.charAt(k)) {
j++;
k++;
}
if (j == end) {
return i - sourceOffset;
}
}
}
return -1;
}
public static List<StringRef> split(String str, String splitBy) {
int len = str.length();
int targetLen = splitBy.length();
if (len == 0 || targetLen == 0) {
return Collections.emptyList();
}
int pos = -targetLen;
List<StringRef> list = new ArrayList<StringRef>();
while (true) {
int start = pos + targetLen;
pos = indexOf(str, 0, len, splitBy, 0, targetLen, start);
if (pos == -1) {
if (start != len) {
list.add(subString(str, start, len));
}
break;
} else {
list.add(subString(str, start, pos));
}
}
return list;
}
public int hashCode() {
int h = hash;
int len = length;
if (h == 0 && len > 0) {
int off = offset;
String str = this.refStr;
for (int i = 0; i < len; i++) {
h = 31 * h + str.charAt(off++);
}
hash = h;
}
return h;
}
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof StringRef)) {
return false;
}
StringRef otherSlice = (StringRef) other;
int len = this.length;
if (len != otherSlice.length) {
return false;
}
int i = offset;
int j = otherSlice.offset;
String refStr = this.refStr;
String otherRefStr = otherSlice.refStr;
while (len-- != 0) {
if (refStr.charAt(i++) != otherRefStr.charAt(j++)) {
return false;
}
}
return true;
}
@NotNull
@Override
public String toString() {
int len = this.length;
if (len == 0) {
return "";
}
int offset = this.offset;
return refStr.substring(offset, offset + len);
}
}
package jadx.gui.utils; package jadx.gui.utils.search;
import java.util.ArrayList;
import java.util.List;
import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory; import com.googlecode.concurrenttrees.radix.node.concrete.DefaultCharArrayNodeFactory;
import com.googlecode.concurrenttrees.suffix.ConcurrentSuffixTree; import com.googlecode.concurrenttrees.suffix.ConcurrentSuffixTree;
public class SuffixTree<V> { public class SuffixTree<V> extends SearchIndex<V> {
private final ConcurrentSuffixTree<V> tree; private final ConcurrentSuffixTree<V> tree;
...@@ -11,6 +14,7 @@ public class SuffixTree<V> { ...@@ -11,6 +14,7 @@ public class SuffixTree<V> {
this.tree = new ConcurrentSuffixTree<V>(new DefaultCharArrayNodeFactory()); this.tree = new ConcurrentSuffixTree<V>(new DefaultCharArrayNodeFactory());
} }
@Override
public void put(String str, V value) { public void put(String str, V value) {
if (str == null || str.isEmpty()) { if (str == null || str.isEmpty()) {
return; return;
...@@ -18,10 +22,17 @@ public class SuffixTree<V> { ...@@ -18,10 +22,17 @@ public class SuffixTree<V> {
tree.putIfAbsent(str, value); tree.putIfAbsent(str, value);
} }
public Iterable<V> getValuesForKeysContaining(String str) { @Override
return tree.getValuesForKeysContaining(str); public List<V> getValuesForKeysContaining(String str) {
Iterable<V> resultsIt = tree.getValuesForKeysContaining(str);
List<V> list = new ArrayList<V>();
for (V v : resultsIt) {
list.add(v);
}
return list;
} }
@Override
public int size() { public int size() {
return tree.size(); return tree.size();
} }
......
package jadx.gui.utils; package jadx.gui.utils.search;
import jadx.api.JavaClass; import jadx.api.JavaClass;
import jadx.api.JavaField; import jadx.api.JavaField;
...@@ -8,6 +8,8 @@ import jadx.core.codegen.CodeWriter; ...@@ -8,6 +8,8 @@ import jadx.core.codegen.CodeWriter;
import jadx.gui.treemodel.CodeNode; import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JNode;
import jadx.gui.ui.CommonSearchDialog; import jadx.gui.ui.CommonSearchDialog;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.JNodeCache;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
...@@ -19,42 +21,57 @@ public class TextSearchIndex { ...@@ -19,42 +21,57 @@ public class TextSearchIndex {
private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class); private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class);
private SuffixTree<JNode> clsNamesTree; private final JNodeCache nodeCache;
private SuffixTree<JNode> mthNamesTree; private final boolean useFastSearch;
private SuffixTree<JNode> fldNamesTree;
private SuffixTree<CodeNode> codeTree; private SearchIndex<JNode> clsNamesIndex;
private SearchIndex<JNode> mthNamesIndex;
private SearchIndex<JNode> fldNamesIndex;
private SearchIndex<CodeNode> codeIndex;
private List<JavaClass> skippedClasses = new ArrayList<JavaClass>(); private List<JavaClass> skippedClasses = new ArrayList<JavaClass>();
public TextSearchIndex() { public TextSearchIndex(JNodeCache nodeCache, boolean useFastSearch) {
clsNamesTree = new SuffixTree<JNode>(); this.nodeCache = nodeCache;
mthNamesTree = new SuffixTree<JNode>(); this.useFastSearch = useFastSearch;
fldNamesTree = new SuffixTree<JNode>(); this.clsNamesIndex = initIndex();
codeTree = new SuffixTree<CodeNode>(); this.mthNamesIndex = initIndex();
this.fldNamesIndex = initIndex();
this.codeIndex = useFastSearch ? new SuffixTree<CodeNode>() : new CodeIndex<CodeNode>();
}
private <T> SearchIndex<T> initIndex() {
return useFastSearch ? new SuffixTree<T>() : new SimpleIndex<T>();
} }
public void indexNames(JavaClass cls) { public void indexNames(JavaClass cls) {
clsNamesTree.put(cls.getFullName(), JNode.makeFrom(cls)); clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls));
for (JavaMethod mth : cls.getMethods()) { for (JavaMethod mth : cls.getMethods()) {
mthNamesTree.put(mth.getFullName(), JNode.makeFrom(mth)); mthNamesIndex.put(mth.getFullName(), this.nodeCache.makeFrom(mth));
} }
for (JavaField fld : cls.getFields()) { for (JavaField fld : cls.getFields()) {
fldNamesTree.put(fld.getFullName(), JNode.makeFrom(fld)); fldNamesIndex.put(fld.getFullName(), nodeCache.makeFrom(fld));
} }
for (JavaClass innerCls : cls.getInnerClasses()) { for (JavaClass innerCls : cls.getInnerClasses()) {
indexNames(innerCls); indexNames(innerCls);
} }
} }
public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, String[] lines) { public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, List<StringRef> lines) {
try { try {
int count = lines.length; boolean strRefSupported = codeIndex.isStringRefSupported();
int count = lines.size();
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
String line = lines[i]; StringRef line = lines.get(i);
if (!line.isEmpty()) { if (line.length() != 0 && line.charAt(0) != '}') {
int lineNum = i + 1; int lineNum = i + 1;
JavaNode node = linesInfo.getJavaNodeByLine(lineNum); JavaNode node = linesInfo.getJavaNodeByLine(lineNum);
codeTree.put(line, new CodeNode(node == null ? cls : node, lineNum, line)); CodeNode codeNode = new CodeNode(nodeCache.makeFrom(node == null ? cls : node), lineNum, line);
if (strRefSupported) {
codeIndex.put(line, codeNode);
} else {
codeIndex.put(line.toString(), codeNode);
}
} }
} }
} catch (Exception e) { } catch (Exception e) {
...@@ -62,36 +79,30 @@ public class TextSearchIndex { ...@@ -62,36 +79,30 @@ public class TextSearchIndex {
} }
} }
public Iterable<JNode> searchClsName(String text) { public List<JNode> searchClsName(String text) {
return clsNamesTree.getValuesForKeysContaining(text); return clsNamesIndex.getValuesForKeysContaining(text);
} }
public Iterable<JNode> searchMthName(String text) { public List<JNode> searchMthName(String text) {
return mthNamesTree.getValuesForKeysContaining(text); return mthNamesIndex.getValuesForKeysContaining(text);
} }
public Iterable<JNode> searchFldName(String text) { public List<JNode> searchFldName(String text) {
return fldNamesTree.getValuesForKeysContaining(text); return fldNamesIndex.getValuesForKeysContaining(text);
} }
public Iterable<CodeNode> searchCode(String text) { public List<CodeNode> searchCode(String text) {
Iterable<CodeNode> items; List<CodeNode> items;
if (codeTree.size() > 0) { if (codeIndex.size() > 0) {
items = codeTree.getValuesForKeysContaining(text); items = codeIndex.getValuesForKeysContaining(text);
if (skippedClasses.isEmpty()) { if (skippedClasses.isEmpty()) {
return items; return items;
} }
} else { } else {
items = null; items = new ArrayList<CodeNode>();
}
List<CodeNode> list = new ArrayList<CodeNode>();
if (items != null) {
for (CodeNode item : items) {
list.add(item);
}
} }
addSkippedClasses(list, text); addSkippedClasses(items, text);
return list; return items;
} }
private void addSkippedClasses(List<CodeNode> list, String text) { private void addSkippedClasses(List<CodeNode> list, String text) {
...@@ -114,8 +125,8 @@ public class TextSearchIndex { ...@@ -114,8 +125,8 @@ public class TextSearchIndex {
} }
int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos); int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos);
int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length()); int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length());
String line = code.substring(lineStart, lineEnd == -1 ? code.length() : lineEnd); StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd);
list.add(new CodeNode(javaClass, -pos, line.trim())); list.add(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim()));
return lineEnd; return lineEnd;
} }
......
...@@ -62,6 +62,7 @@ preferences.cfg=Generate methods CFG graphs (in 'dot' format) ...@@ -62,6 +62,7 @@ preferences.cfg=Generate methods CFG graphs (in 'dot' format)
preferences.raw_cfg=Generate RAW CFG graphs preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Editor font preferences.font=Editor font
preferences.fast_search=Fast search (uses more memory) preferences.fast_search=Fast search (uses more memory)
preferences.start_jobs=Auto start background decompilation
preferences.select_font=Select preferences.select_font=Select
preferences.deobfuscation_on=Enable deobfuscation preferences.deobfuscation_on=Enable deobfuscation
preferences.deobfuscation_force=Force rewrite deobfuscation map file preferences.deobfuscation_force=Force rewrite deobfuscation map file
......
package jadx.gui.tests
import jadx.gui.utils.search.StringRef
import spock.lang.Specification
import static jadx.gui.utils.search.StringRef.fromStr
import static jadx.gui.utils.search.StringRef.subString
class TestStringRef extends Specification {
def "test substring"() {
expect:
s1.toString() == expected
s1 == fromStr(expected)
where:
s1 | expected
fromStr("a") | "a"
subString("a", 0) | "a"
subString("a", 1) | ""
subString("a", 0, 0) | ""
subString("a", 0, 1) | "a"
subString("abc", 1, 2) | "b"
subString("abc", 2) | "c"
subString("abc", 2, 3) | "c"
}
def "compare with original substring"() {
expect:
s == expected
where:
s | expected
"a".substring(0) | "a"
"a".substring(1) | ""
"a".substring(0, 0) | ""
"a".substring(0, 1) | "a"
}
def "test trim"() {
expect:
s.trim().toString() == expected
where:
s | expected
fromStr("a") | "a"
fromStr(" a ") | "a"
fromStr("\ta") | "a"
subString("a b c", 1) | "b c"
subString("a b\tc", 1, 4) | "b"
subString("a b\tc", 2, 3) | "b"
}
def "test split"() {
expect:
StringRef.split(s, d) == (expected as String[]).collect { fromStr(it) }
if (!Arrays.equals(s.split(d), (expected).toArray(new String[0]))) {
throw new IllegalArgumentException("Don't match with original split: "
+ " s='" + s + "' d='" + d
+ "', expected:" + expected + ", got: " + Arrays.toString(s.split(d)));
}
where:
s | d | expected
"abc" | "b" | ["a", "c"]
"abc" | "a" | ["", "bc"]
"abc" | "c" | ["ab"]
"abc" | "d" | ["abc"]
"abbbc" | "b" | ["a", "", "", "c"]
"abbbc" | "bb" | ["a", "bc"]
"abbbc" | "bbb" | ["a", "c"]
"abbbc" | "bbc" | ["ab"]
"abbbc" | "bbbc" | ["a"]
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment