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 {
protected String outDirName;
@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)")
protected boolean fallbackMode = false;
......
......@@ -5,7 +5,7 @@
<level>INFO</level>
</filter>
<encoder>
<pattern>%-5level - %msg%n</pattern>
<pattern>%d{HH:mm:ss} %-5level - %msg%n</pattern>
</encoder>
</appender>
......
......@@ -2,7 +2,7 @@ package jadx.gui.jobs;
import jadx.gui.ui.ProgressPanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.TextSearchIndex;
import jadx.gui.utils.search.TextSearchIndex;
import jadx.gui.utils.Utils;
import javax.swing.SwingUtilities;
......
......@@ -3,12 +3,15 @@ package jadx.gui.jobs;
import jadx.api.JavaClass;
import jadx.core.codegen.CodeWriter;
import jadx.gui.JadxWrapper;
import jadx.gui.settings.JadxSettings;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.TextSearchIndex;
import jadx.gui.utils.JNodeCache;
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.slf4j.Logger;
......@@ -20,15 +23,16 @@ public class IndexJob extends BackgroundJob {
private final CacheObject cache;
private final boolean useFastSearch;
public IndexJob(JadxWrapper wrapper, JadxSettings settings, CacheObject cache) {
super(wrapper, settings.getThreadsCount());
this.useFastSearch = settings.isUseFastSearch();
public IndexJob(JadxWrapper wrapper, CacheObject cache, int threadsCount, boolean useFastSearch) {
super(wrapper, threadsCount);
this.useFastSearch = useFastSearch;
this.cache = cache;
}
protected void runJob() {
final TextSearchIndex index = new TextSearchIndex();
final CodeUsageInfo usageInfo = new CodeUsageInfo();
JNodeCache nodeCache = cache.getNodeCache();
final TextSearchIndex index = new TextSearchIndex(nodeCache, useFastSearch);
final CodeUsageInfo usageInfo = new CodeUsageInfo(nodeCache);
cache.setTextIndex(index);
cache.setUsageInfo(usageInfo);
for (final JavaClass cls : wrapper.getClasses()) {
......@@ -39,10 +43,10 @@ public class IndexJob extends BackgroundJob {
index.indexNames(cls);
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
String[] lines = splitIntoLines(cls);
List<StringRef> lines = splitLines(cls);
usageInfo.processClass(cls, linesInfo, lines);
if (useFastSearch && Utils.isFreeMemoryAvailable()) {
if (Utils.isFreeMemoryAvailable()) {
index.indexCode(cls, linesInfo, lines);
} else {
index.classCodeIndexSkipped(cls);
......@@ -56,11 +60,11 @@ public class IndexJob extends BackgroundJob {
}
@NotNull
protected String[] splitIntoLines(JavaClass cls) {
String[] lines = cls.getCode().split(CodeWriter.NL);
int count = lines.length;
for (int i = 0; i < count; i++) {
lines[i] = lines[i].trim();
protected List<StringRef> splitLines(JavaClass cls) {
List<StringRef> lines = StringRef.split(cls.getCode(), CodeWriter.NL);
int size = lines.size();
for (int i = 0; i < size; i++) {
lines.set(i, lines.get(i).trim());
}
return lines;
}
......
......@@ -29,6 +29,11 @@ public class JadxSettings extends JadxCLIArgs {
private List<String> recentFiles = new ArrayList<String>();
private String fontStr = "";
private boolean useFastSearch = false;
private boolean autoStartJobs = true;
public JadxSettings() {
setSkipResources(true);
}
public void sync() {
JadxSettingsAdapter.store(this);
......@@ -137,13 +142,22 @@ public class JadxSettings extends JadxCLIArgs {
}
public boolean isUseFastSearch() {
return useFastSearch;
return false;
// return useFastSearch;
}
public void setUseFastSearch(boolean useFastSearch) {
this.useFastSearch = useFastSearch;
}
public boolean isAutoStartJobs() {
return autoStartJobs;
}
public void setAutoStartJobs(boolean autoStartJobs) {
this.autoStartJobs = autoStartJobs;
}
public Font getFont() {
if (fontStr.isEmpty()) {
return DEFAULT_FONT;
......
......@@ -24,7 +24,6 @@ import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
......@@ -57,7 +56,7 @@ public class JadxSettingsWindow extends JDialog {
private void initUI() {
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.add(makeDeobfuscationGroup());
panel.add(makeOtherGroup());
......@@ -102,7 +101,6 @@ public class JadxSettingsWindow extends JDialog {
}
private SettingsGroup makeDeobfuscationGroup() {
JCheckBox deobfOn = new JCheckBox();
deobfOn.setSelected(settings.isDeobfuscationOn());
deobfOn.addItemListener(new ItemListener() {
......@@ -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();
fastSearch.setEnabled(false);
fastSearch.setSelected(settings.isUseFastSearch());
fastSearch.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
......@@ -257,6 +264,7 @@ public class JadxSettingsWindow extends JDialog {
other.addRow(NLS.str("preferences.raw_cfg"), rawCfg);
other.addRow(NLS.str("preferences.font"), fontBtn);
other.addRow(NLS.str("preferences.fast_search"), fastSearch);
other.addRow(NLS.str("preferences.start_jobs"), autoStartJobs);
return other;
}
......
package jadx.gui.treemodel;
import jadx.api.JavaNode;
import jadx.gui.utils.search.StringRef;
import javax.swing.Icon;
......@@ -10,12 +11,12 @@ public class CodeNode extends JNode {
private final JNode jNode;
private final JClass jParent;
private final String line;
private final StringRef line;
private final int lineNum;
public CodeNode(JavaNode javaNode, int lineNum, String line) {
this.jNode = makeFrom(javaNode);
this.jParent = jNode.getJParent();
public CodeNode(JNode jNode, int lineNum, StringRef line) {
this.jNode = jNode;
this.jParent = this.jNode.getJParent();
this.line = line;
this.lineNum = lineNum;
}
......@@ -54,7 +55,7 @@ public class CodeNode extends JNode {
@Override
public String makeDescString() {
return line;
return line.toString();
}
@Override
......
package jadx.gui.treemodel;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.utils.exceptions.JadxRuntimeException;
import javax.swing.Icon;
import javax.swing.tree.DefaultMutableTreeNode;
......@@ -12,24 +8,6 @@ import javax.swing.tree.DefaultMutableTreeNode;
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
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();
......
......@@ -7,7 +7,7 @@ import jadx.gui.treemodel.TextNode;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.NLS;
import jadx.gui.utils.Position;
import jadx.gui.utils.TextSearchIndex;
import jadx.gui.utils.search.TextSearchIndex;
import javax.swing.BorderFactory;
import javax.swing.Box;
......@@ -56,7 +56,7 @@ public abstract class CommonSearchDialog extends JDialog {
private static final Logger LOG = LoggerFactory.getLogger(CommonSearchDialog.class);
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 CacheObject cache;
......@@ -328,7 +328,10 @@ public abstract class CommonSearchDialog extends JDialog {
textArea.setRows(1);
textArea.setColumns(textArea.getText().length());
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;
}
......
......@@ -214,7 +214,8 @@ class ContentArea extends RSyntaxTextArea {
return;
}
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);
}
......
......@@ -194,20 +194,23 @@ public class MainWindow extends JFrame {
protected void resetCache() {
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.setIndexJob(new IndexJob(wrapper, settings, cacheObject));
cacheObject.setIndexJob(new IndexJob(wrapper, cacheObject, threadsCount, settings.isUseFastSearch()));
}
private synchronized void runBackgroundJobs() {
cancelBackgroundJobs();
backgroundWorker = new BackgroundWorker(cacheObject, progressPane);
new Timer().schedule(new TimerTask() {
@Override
public void run() {
backgroundWorker.exec();
}
}, 1000);
if (settings.isAutoStartJobs()) {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
backgroundWorker.exec();
}
}, 1000);
}
}
public synchronized void cancelBackgroundJobs() {
......
package jadx.gui.ui;
import jadx.gui.utils.NLS;
import jadx.gui.utils.TextSearchIndex;
import jadx.gui.utils.search.TextSearchIndex;
import jadx.gui.utils.TextStandardActions;
import javax.swing.BorderFactory;
......
......@@ -2,6 +2,7 @@ package jadx.gui.utils;
import jadx.gui.jobs.DecompileJob;
import jadx.gui.jobs.IndexJob;
import jadx.gui.utils.search.TextSearchIndex;
import org.jetbrains.annotations.Nullable;
......@@ -13,10 +14,12 @@ public class CacheObject {
private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo;
private String lastSearch;
private JNodeCache jNodeCache = new JNodeCache();
public void reset() {
textIndex = null;
lastSearch = null;
jNodeCache = new JNodeCache();
usageInfo = null;
}
......@@ -62,4 +65,8 @@ public class CacheObject {
public void setIndexJob(IndexJob indexJob) {
this.indexJob = indexJob;
}
public JNodeCache getNodeCache() {
return jNodeCache;
}
}
......@@ -5,6 +5,7 @@ import jadx.api.JavaClass;
import jadx.api.JavaNode;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.search.StringRef;
import java.util.ArrayList;
import java.util.Collections;
......@@ -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>();
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();
for (Map.Entry<CodePosition, JavaNode> entry : usage.entrySet()) {
CodePosition codePosition = entry.getKey();
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,
CodeLinesInfo linesInfo, CodePosition codePosition, String[] lines) {
CodeLinesInfo linesInfo, CodePosition codePosition, List<StringRef> lines) {
UsageInfo usageInfo = usageMap.get(jNode);
if (usageInfo == null) {
usageInfo = new UsageInfo();
......@@ -42,8 +49,9 @@ public class CodeUsageInfo {
}
int line = codePosition.getLine();
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
String codeLine = lines[line - 1].trim();
CodeNode codeNode = new CodeNode(javaNodeByLine == null ? javaClass : javaNodeByLine, line, codeLine);
StringRef codeLine = lines.get(line - 1);
JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
CodeNode codeNode = new CodeNode(node, line, codeLine);
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 {
long allocatedMemory = runtime.totalMemory();
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(", max: ").append(format(maxMemory));
sb.append(", free: ").append(format(freeMemory));
sb.append(", total free: ").append(format(freeMemory + maxMemory - allocatedMemory));
sb.append(", max: ").append(format(maxMemory));
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.suffix.ConcurrentSuffixTree;
public class SuffixTree<V> {
public class SuffixTree<V> extends SearchIndex<V> {
private final ConcurrentSuffixTree<V> tree;
......@@ -11,6 +14,7 @@ public class SuffixTree<V> {
this.tree = new ConcurrentSuffixTree<V>(new DefaultCharArrayNodeFactory());
}
@Override
public void put(String str, V value) {
if (str == null || str.isEmpty()) {
return;
......@@ -18,10 +22,17 @@ public class SuffixTree<V> {
tree.putIfAbsent(str, value);
}
public Iterable<V> getValuesForKeysContaining(String str) {
return tree.getValuesForKeysContaining(str);
@Override
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() {
return tree.size();
}
......
package jadx.gui.utils;
package jadx.gui.utils.search;
import jadx.api.JavaClass;
import jadx.api.JavaField;
......@@ -8,6 +8,8 @@ import jadx.core.codegen.CodeWriter;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.CommonSearchDialog;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.JNodeCache;
import java.util.ArrayList;
import java.util.List;
......@@ -19,42 +21,57 @@ public class TextSearchIndex {
private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class);
private SuffixTree<JNode> clsNamesTree;
private SuffixTree<JNode> mthNamesTree;
private SuffixTree<JNode> fldNamesTree;
private SuffixTree<CodeNode> codeTree;
private final JNodeCache nodeCache;
private final boolean useFastSearch;
private SearchIndex<JNode> clsNamesIndex;
private SearchIndex<JNode> mthNamesIndex;
private SearchIndex<JNode> fldNamesIndex;
private SearchIndex<CodeNode> codeIndex;
private List<JavaClass> skippedClasses = new ArrayList<JavaClass>();
public TextSearchIndex() {
clsNamesTree = new SuffixTree<JNode>();
mthNamesTree = new SuffixTree<JNode>();
fldNamesTree = new SuffixTree<JNode>();
codeTree = new SuffixTree<CodeNode>();
public TextSearchIndex(JNodeCache nodeCache, boolean useFastSearch) {
this.nodeCache = nodeCache;
this.useFastSearch = useFastSearch;
this.clsNamesIndex = initIndex();
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) {
clsNamesTree.put(cls.getFullName(), JNode.makeFrom(cls));
clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls));
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()) {
fldNamesTree.put(fld.getFullName(), JNode.makeFrom(fld));
fldNamesIndex.put(fld.getFullName(), nodeCache.makeFrom(fld));
}
for (JavaClass innerCls : cls.getInnerClasses()) {
indexNames(innerCls);
}
}
public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, String[] lines) {
public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, List<StringRef> lines) {
try {
int count = lines.length;
boolean strRefSupported = codeIndex.isStringRefSupported();
int count = lines.size();
for (int i = 0; i < count; i++) {
String line = lines[i];
if (!line.isEmpty()) {
StringRef line = lines.get(i);
if (line.length() != 0 && line.charAt(0) != '}') {
int lineNum = i + 1;
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) {
......@@ -62,36 +79,30 @@ public class TextSearchIndex {
}
}
public Iterable<JNode> searchClsName(String text) {
return clsNamesTree.getValuesForKeysContaining(text);
public List<JNode> searchClsName(String text) {
return clsNamesIndex.getValuesForKeysContaining(text);
}
public Iterable<JNode> searchMthName(String text) {
return mthNamesTree.getValuesForKeysContaining(text);
public List<JNode> searchMthName(String text) {
return mthNamesIndex.getValuesForKeysContaining(text);
}
public Iterable<JNode> searchFldName(String text) {
return fldNamesTree.getValuesForKeysContaining(text);
public List<JNode> searchFldName(String text) {
return fldNamesIndex.getValuesForKeysContaining(text);
}
public Iterable<CodeNode> searchCode(String text) {
Iterable<CodeNode> items;
if (codeTree.size() > 0) {
items = codeTree.getValuesForKeysContaining(text);
public List<CodeNode> searchCode(String text) {
List<CodeNode> items;
if (codeIndex.size() > 0) {
items = codeIndex.getValuesForKeysContaining(text);
if (skippedClasses.isEmpty()) {
return items;
}
} else {
items = null;
}
List<CodeNode> list = new ArrayList<CodeNode>();
if (items != null) {
for (CodeNode item : items) {
list.add(item);
}
items = new ArrayList<CodeNode>();
}
addSkippedClasses(list, text);
return list;
addSkippedClasses(items, text);
return items;
}
private void addSkippedClasses(List<CodeNode> list, String text) {
......@@ -114,8 +125,8 @@ public class TextSearchIndex {
}
int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos);
int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length());
String line = code.substring(lineStart, lineEnd == -1 ? code.length() : lineEnd);
list.add(new CodeNode(javaClass, -pos, line.trim()));
StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd);
list.add(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim()));
return lineEnd;
}
......
......@@ -62,6 +62,7 @@ preferences.cfg=Generate methods CFG graphs (in 'dot' format)
preferences.raw_cfg=Generate RAW CFG graphs
preferences.font=Editor font
preferences.fast_search=Fast search (uses more memory)
preferences.start_jobs=Auto start background decompilation
preferences.select_font=Select
preferences.deobfuscation_on=Enable deobfuscation
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