diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index f19b9b2d6..bf88d1ce3 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -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; diff --git a/jadx-cli/src/main/resources/logback.xml b/jadx-cli/src/main/resources/logback.xml index 6a1e61641..670c71320 100644 --- a/jadx-cli/src/main/resources/logback.xml +++ b/jadx-cli/src/main/resources/logback.xml @@ -5,7 +5,7 @@ INFO - %-5level - %msg%n + %d{HH:mm:ss} %-5level - %msg%n diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java index 0614e7cfe..e0f3992d0 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/BackgroundWorker.java @@ -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; diff --git a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java index 6039f9a21..83022ec59 100644 --- a/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java +++ b/jadx-gui/src/main/java/jadx/gui/jobs/IndexJob.java @@ -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 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 splitLines(JavaClass cls) { + List 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; } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 1986fa1ef..73eeda752 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -29,6 +29,11 @@ public class JadxSettings extends JadxCLIArgs { private List recentFiles = new ArrayList(); 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; diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java index 89f281725..001d0bd9a 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsWindow.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java index 192299791..592d7023b 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/CodeNode.java @@ -1,6 +1,7 @@ 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 diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 82e6ecb15..5017fe81f 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -1,10 +1,6 @@ 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(); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java index d4c6b4852..194dc6a1c 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/CommonSearchDialog.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java b/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java index b79654ddb..18cae067b 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/ContentArea.java @@ -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); } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 00937d071..fec78a77c 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -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() { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java index 564cba57f..6ba405b78 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/SearchDialog.java @@ -1,7 +1,7 @@ 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; diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java index 63f00a33f..1fd4955be 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java @@ -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; + } } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java b/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java index 29a665141..ee6213ace 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/CodeUsageInfo.java @@ -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 usageMap = new HashMap(); - public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, String[] lines) { + public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, List lines) { Map usage = javaClass.getUsageMap(); for (Map.Entry 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 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); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java new file mode 100644 index 000000000..1a88411c0 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java @@ -0,0 +1,50 @@ +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 cache = new HashMap(); + + 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()); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/TextSearchIndex.java deleted file mode 100644 index d4b7278b7..000000000 --- a/jadx-gui/src/main/java/jadx/gui/utils/TextSearchIndex.java +++ /dev/null @@ -1,133 +0,0 @@ -package jadx.gui.utils; - -import jadx.api.JavaClass; -import jadx.api.JavaField; -import jadx.api.JavaMethod; -import jadx.api.JavaNode; -import jadx.core.codegen.CodeWriter; -import jadx.gui.treemodel.CodeNode; -import jadx.gui.treemodel.JNode; -import jadx.gui.ui.CommonSearchDialog; - -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TextSearchIndex { - - private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class); - - private SuffixTree clsNamesTree; - private SuffixTree mthNamesTree; - private SuffixTree fldNamesTree; - private SuffixTree codeTree; - - private List skippedClasses = new ArrayList(); - - public TextSearchIndex() { - clsNamesTree = new SuffixTree(); - mthNamesTree = new SuffixTree(); - fldNamesTree = new SuffixTree(); - codeTree = new SuffixTree(); - } - - public void indexNames(JavaClass cls) { - clsNamesTree.put(cls.getFullName(), JNode.makeFrom(cls)); - for (JavaMethod mth : cls.getMethods()) { - mthNamesTree.put(mth.getFullName(), JNode.makeFrom(mth)); - } - for (JavaField fld : cls.getFields()) { - fldNamesTree.put(fld.getFullName(), JNode.makeFrom(fld)); - } - for (JavaClass innerCls : cls.getInnerClasses()) { - indexNames(innerCls); - } - } - - public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, String[] lines) { - try { - int count = lines.length; - for (int i = 0; i < count; i++) { - String line = lines[i]; - if (!line.isEmpty()) { - int lineNum = i + 1; - JavaNode node = linesInfo.getJavaNodeByLine(lineNum); - codeTree.put(line, new CodeNode(node == null ? cls : node, lineNum, line)); - } - } - } catch (Exception e) { - LOG.warn("Failed to index class: {}", cls, e); - } - } - - public Iterable searchClsName(String text) { - return clsNamesTree.getValuesForKeysContaining(text); - } - - public Iterable searchMthName(String text) { - return mthNamesTree.getValuesForKeysContaining(text); - } - - public Iterable searchFldName(String text) { - return fldNamesTree.getValuesForKeysContaining(text); - } - - public Iterable searchCode(String text) { - Iterable items; - if (codeTree.size() > 0) { - items = codeTree.getValuesForKeysContaining(text); - if (skippedClasses.isEmpty()) { - return items; - } - } else { - items = null; - } - List list = new ArrayList(); - if (items != null) { - for (CodeNode item : items) { - list.add(item); - } - } - addSkippedClasses(list, text); - return list; - } - - private void addSkippedClasses(List list, String text) { - for (JavaClass javaClass : skippedClasses) { - String code = javaClass.getCode(); - int pos = 0; - while (pos != -1) { - pos = searchNext(list, text, javaClass, code, pos); - } - if (list.size() > CommonSearchDialog.MAX_RESULTS_COUNT) { - return; - } - } - } - - private int searchNext(List list, String text, JavaNode javaClass, String code, int startPos) { - int pos = code.indexOf(text, startPos); - if (pos == -1) { - return -1; - } - 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())); - return lineEnd; - } - - public void classCodeIndexSkipped(JavaClass cls) { - this.skippedClasses.add(cls); - } - - public List getSkippedClasses() { - return skippedClasses; - } - - public int getSkippedCount() { - return skippedClasses.size(); - } -} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java index c51aa64d7..8886b7455 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/Utils.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/Utils.java @@ -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(); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java new file mode 100644 index 000000000..64dc19cc7 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/CodeIndex.java @@ -0,0 +1,51 @@ +package jadx.gui.utils.search; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CodeIndex extends SearchIndex { + + private final List keys = new ArrayList(); + private final List values = new ArrayList(); + + @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 getValuesForKeysContaining(String str) { + int size = size(); + if (size == 0) { + return Collections.emptyList(); + } + List results = new ArrayList(); + 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(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java new file mode 100644 index 000000000..fbd6619b3 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SearchIndex.java @@ -0,0 +1,20 @@ +package jadx.gui.utils.search; + +import java.util.List; + +public abstract class SearchIndex { + + 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 getValuesForKeysContaining(String str); + + public abstract int size(); +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java new file mode 100644 index 000000000..ab27b7d46 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SimpleIndex.java @@ -0,0 +1,38 @@ +package jadx.gui.utils.search; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SimpleIndex extends SearchIndex { + + private final List keys = new ArrayList(); + private final List values = new ArrayList(); + + @Override + public void put(String str, T value) { + keys.add(str); + values.add(value); + } + + @Override + public List getValuesForKeysContaining(String str) { + int size = size(); + if (size == 0) { + return Collections.emptyList(); + } + List results = new ArrayList(); + 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(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/StringRef.java b/jadx-gui/src/main/java/jadx/gui/utils/search/StringRef.java new file mode 100644 index 000000000..37db49605 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/StringRef.java @@ -0,0 +1,181 @@ +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 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 list = new ArrayList(); + 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); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java b/jadx-gui/src/main/java/jadx/gui/utils/search/SuffixTree.java similarity index 55% rename from jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java rename to jadx-gui/src/main/java/jadx/gui/utils/search/SuffixTree.java index d5caf651b..8a04f8edd 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/SuffixTree.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/SuffixTree.java @@ -1,9 +1,12 @@ -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 { +public class SuffixTree extends SearchIndex { private final ConcurrentSuffixTree tree; @@ -11,6 +14,7 @@ public class SuffixTree { this.tree = new ConcurrentSuffixTree(new DefaultCharArrayNodeFactory()); } + @Override public void put(String str, V value) { if (str == null || str.isEmpty()) { return; @@ -18,10 +22,17 @@ public class SuffixTree { tree.putIfAbsent(str, value); } - public Iterable getValuesForKeysContaining(String str) { - return tree.getValuesForKeysContaining(str); + @Override + public List getValuesForKeysContaining(String str) { + Iterable resultsIt = tree.getValuesForKeysContaining(str); + List list = new ArrayList(); + for (V v : resultsIt) { + list.add(v); + } + return list; } + @Override public int size() { return tree.size(); } diff --git a/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java new file mode 100644 index 000000000..b4515282e --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/search/TextSearchIndex.java @@ -0,0 +1,144 @@ +package jadx.gui.utils.search; + +import jadx.api.JavaClass; +import jadx.api.JavaField; +import jadx.api.JavaMethod; +import jadx.api.JavaNode; +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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TextSearchIndex { + + private static final Logger LOG = LoggerFactory.getLogger(TextSearchIndex.class); + + private final JNodeCache nodeCache; + private final boolean useFastSearch; + + private SearchIndex clsNamesIndex; + private SearchIndex mthNamesIndex; + private SearchIndex fldNamesIndex; + private SearchIndex codeIndex; + + private List skippedClasses = new ArrayList(); + + 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() : new CodeIndex(); + } + + private SearchIndex initIndex() { + return useFastSearch ? new SuffixTree() : new SimpleIndex(); + } + + public void indexNames(JavaClass cls) { + clsNamesIndex.put(cls.getFullName(), nodeCache.makeFrom(cls)); + for (JavaMethod mth : cls.getMethods()) { + mthNamesIndex.put(mth.getFullName(), this.nodeCache.makeFrom(mth)); + } + for (JavaField fld : cls.getFields()) { + fldNamesIndex.put(fld.getFullName(), nodeCache.makeFrom(fld)); + } + for (JavaClass innerCls : cls.getInnerClasses()) { + indexNames(innerCls); + } + } + + public void indexCode(JavaClass cls, CodeLinesInfo linesInfo, List lines) { + try { + boolean strRefSupported = codeIndex.isStringRefSupported(); + int count = lines.size(); + for (int i = 0; i < count; i++) { + StringRef line = lines.get(i); + if (line.length() != 0 && line.charAt(0) != '}') { + int lineNum = i + 1; + JavaNode node = linesInfo.getJavaNodeByLine(lineNum); + 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) { + LOG.warn("Failed to index class: {}", cls, e); + } + } + + public List searchClsName(String text) { + return clsNamesIndex.getValuesForKeysContaining(text); + } + + public List searchMthName(String text) { + return mthNamesIndex.getValuesForKeysContaining(text); + } + + public List searchFldName(String text) { + return fldNamesIndex.getValuesForKeysContaining(text); + } + + public List searchCode(String text) { + List items; + if (codeIndex.size() > 0) { + items = codeIndex.getValuesForKeysContaining(text); + if (skippedClasses.isEmpty()) { + return items; + } + } else { + items = new ArrayList(); + } + addSkippedClasses(items, text); + return items; + } + + private void addSkippedClasses(List list, String text) { + for (JavaClass javaClass : skippedClasses) { + String code = javaClass.getCode(); + int pos = 0; + while (pos != -1) { + pos = searchNext(list, text, javaClass, code, pos); + } + if (list.size() > CommonSearchDialog.MAX_RESULTS_COUNT) { + return; + } + } + } + + private int searchNext(List list, String text, JavaNode javaClass, String code, int startPos) { + int pos = code.indexOf(text, startPos); + if (pos == -1) { + return -1; + } + int lineStart = 1 + code.lastIndexOf(CodeWriter.NL, pos); + int lineEnd = code.indexOf(CodeWriter.NL, pos + text.length()); + StringRef line = StringRef.subString(code, lineStart, lineEnd == -1 ? code.length() : lineEnd); + list.add(new CodeNode(nodeCache.makeFrom(javaClass), -pos, line.trim())); + return lineEnd; + } + + public void classCodeIndexSkipped(JavaClass cls) { + this.skippedClasses.add(cls); + } + + public List getSkippedClasses() { + return skippedClasses; + } + + public int getSkippedCount() { + return skippedClasses.size(); + } +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index a6905633a..dcb59db5a 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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 diff --git a/jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy b/jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy new file mode 100644 index 000000000..352978ab8 --- /dev/null +++ b/jadx-gui/src/test/groovy/jadx/gui/tests/TestStringRef.groovy @@ -0,0 +1,70 @@ +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"] + } +}