fix(gui): properly handle excluded classes in code search (#2432)
This commit is contained in:
@@ -28,7 +28,8 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
long start = System.currentTimeMillis();
|
||||
List<List<JavaClass>> result = internalBatches(classes);
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
|
||||
LOG.debug("Build decompilation batches in {}ms for {} classes",
|
||||
System.currentTimeMillis() - start, classes.size());
|
||||
}
|
||||
if (DEBUG_BATCHES) {
|
||||
check(result, classes);
|
||||
@@ -77,7 +78,7 @@ public class DecompilerScheduler implements IDecompileScheduler {
|
||||
result.add(batch);
|
||||
}
|
||||
}
|
||||
if (mergedBatch.size() > 0) {
|
||||
if (!mergedBatch.isEmpty()) {
|
||||
result.add(mergedBatch);
|
||||
}
|
||||
if (DEBUG_BATCHES) {
|
||||
|
||||
@@ -4,31 +4,36 @@ import java.util.regex.Pattern;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.core.dex.nodes.PackageNode;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JResource;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
public class SearchSettings {
|
||||
|
||||
private final String searchString;
|
||||
private final boolean useRegex;
|
||||
private final boolean ignoreCase;
|
||||
private final JavaPackage searchPackage;
|
||||
private final String searchPkgStr;
|
||||
|
||||
private JClass activeCls;
|
||||
private JResource activeResource;
|
||||
private Pattern regexPattern;
|
||||
private ISearchMethod searchMethod;
|
||||
private JavaPackage searchPackage;
|
||||
|
||||
public SearchSettings(String searchString, boolean ignoreCase, boolean useRegex, JavaPackage searchPackage) {
|
||||
public SearchSettings(String searchString, boolean ignoreCase, boolean useRegex, String searchPkgStr) {
|
||||
this.searchString = searchString;
|
||||
this.useRegex = useRegex;
|
||||
this.ignoreCase = ignoreCase;
|
||||
this.searchPackage = searchPackage;
|
||||
this.searchPkgStr = searchPkgStr;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String prepare() {
|
||||
public String prepare(MainWindow mainWindow) {
|
||||
if (useRegex) {
|
||||
try {
|
||||
int flags = ignoreCase ? Pattern.CASE_INSENSITIVE : 0;
|
||||
@@ -37,6 +42,14 @@ public class SearchSettings {
|
||||
return "Invalid Regex: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
if (!searchPkgStr.isBlank()) {
|
||||
JadxDecompiler decompiler = mainWindow.getWrapper().getDecompiler();
|
||||
PackageNode pkg = decompiler.getRoot().resolvePackage(searchPkgStr);
|
||||
if (pkg == null) {
|
||||
return NLS.str("search_dialog.package_not_found");
|
||||
}
|
||||
searchPackage = pkg.getJavaNode();
|
||||
}
|
||||
searchMethod = ISearchMethod.build(this);
|
||||
return null;
|
||||
}
|
||||
@@ -57,6 +70,10 @@ public class SearchSettings {
|
||||
return this.searchPackage;
|
||||
}
|
||||
|
||||
public boolean isInSearchPkg(JavaClass cls) {
|
||||
return cls.getJavaPackage().isDescendantOf(searchPackage);
|
||||
}
|
||||
|
||||
public String getSearchString() {
|
||||
return this.searchString;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.gui.search.providers;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
@@ -27,34 +28,44 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
||||
|
||||
private final ICodeCache codeCache;
|
||||
private final JadxWrapper wrapper;
|
||||
private final @Nullable Set<JavaClass> includedClasses;
|
||||
|
||||
private @Nullable String code;
|
||||
private int clsNum = 0;
|
||||
private int pos = 0;
|
||||
|
||||
public CodeSearchProvider(MainWindow mw, SearchSettings searchSettings, List<JavaClass> classes) {
|
||||
public CodeSearchProvider(MainWindow mw, SearchSettings searchSettings,
|
||||
List<JavaClass> classes, @Nullable Set<JavaClass> includedClasses) {
|
||||
super(mw, searchSettings, classes);
|
||||
this.codeCache = mw.getWrapper().getArgs().getCodeCache();
|
||||
this.wrapper = mw.getWrapper();
|
||||
this.includedClasses = includedClasses;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JNode next(Cancelable cancelable) {
|
||||
Set<JavaClass> inclCls = includedClasses;
|
||||
while (true) {
|
||||
if (cancelable.isCanceled() || clsNum >= classes.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
JavaClass cls = classes.get(clsNum);
|
||||
String clsCode = code;
|
||||
if (clsCode == null && !cls.isInner() && !cls.isNoCode()) {
|
||||
clsCode = getClassCode(cls, codeCache);
|
||||
}
|
||||
if (clsCode != null) {
|
||||
JNode newResult = searchNext(cls, clsCode);
|
||||
if (newResult != null) {
|
||||
code = clsCode;
|
||||
return newResult;
|
||||
if (inclCls == null || inclCls.contains(cls)) {
|
||||
String clsCode = code;
|
||||
if (clsCode == null && !cls.isInner() && !cls.isNoCode()) {
|
||||
clsCode = getClassCode(cls, codeCache);
|
||||
}
|
||||
if (clsCode != null) {
|
||||
JNode newResult = searchNext(cls, clsCode);
|
||||
if (newResult != null) {
|
||||
code = clsCode;
|
||||
return newResult;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// force decompilation for not included classes
|
||||
cls.decompile();
|
||||
}
|
||||
clsNum++;
|
||||
pos = 0;
|
||||
@@ -62,8 +73,7 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JNode searchNext(JavaClass javaClass, String clsCode) {
|
||||
private @Nullable JNode searchNext(JavaClass javaClass, String clsCode) {
|
||||
int newPos = searchMth.find(clsCode, searchStr, pos);
|
||||
if (newPos == -1) {
|
||||
return null;
|
||||
@@ -99,9 +109,10 @@ public final class CodeSearchProvider extends BaseSearchProvider {
|
||||
if (code != null) {
|
||||
return code;
|
||||
}
|
||||
// start decompilation
|
||||
return javaClass.getCode();
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Failed to get class code: " + javaClass, e);
|
||||
LOG.warn("Failed to get class code: {}", javaClass, e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package jadx.gui.search.providers;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
@@ -39,14 +41,16 @@ public class CommentSearchProvider implements ISearchProvider {
|
||||
private final CacheObject cacheObject;
|
||||
private final JadxProject project;
|
||||
private final SearchSettings searchSettings;
|
||||
private final Set<JavaClass> searchClsSet;
|
||||
|
||||
private int progress = 0;
|
||||
|
||||
public CommentSearchProvider(MainWindow mw, SearchSettings searchSettings) {
|
||||
public CommentSearchProvider(MainWindow mw, SearchSettings searchSettings, List<JavaClass> searchClasses) {
|
||||
this.wrapper = mw.getWrapper();
|
||||
this.cacheObject = mw.getCacheObject();
|
||||
this.project = mw.getProject();
|
||||
this.searchSettings = searchSettings;
|
||||
this.searchClsSet = new HashSet<>(searchClasses);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,17 +74,12 @@ public class CommentSearchProvider implements ISearchProvider {
|
||||
boolean all = searchSettings.getSearchString().isEmpty();
|
||||
if (all || searchSettings.isMatch(comment.getComment())) {
|
||||
JNode refNode = getRefNode(comment);
|
||||
if (refNode != null) {
|
||||
if (searchSettings.getSearchPackage() != null
|
||||
&& !refNode.getRootClass().getCls().getJavaPackage().isDescendantOf(searchSettings.getSearchPackage())) {
|
||||
return null;
|
||||
}
|
||||
JClass activeCls = searchSettings.getActiveCls();
|
||||
if (activeCls == null || Objects.equals(activeCls, refNode.getRootClass())) {
|
||||
return getCommentNode(comment, refNode);
|
||||
}
|
||||
} else {
|
||||
if (refNode == null) {
|
||||
LOG.warn("Failed to get ref node for comment: {}", comment);
|
||||
return null;
|
||||
}
|
||||
if (searchClsSet.contains(refNode.getRootClass().getCls())) {
|
||||
return getCommentNode(comment, refNode);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -22,6 +22,10 @@ public class MergedSearchProvider implements ISearchProvider {
|
||||
list.add(provider);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
public void prepare() {
|
||||
current = list.isEmpty() ? -1 : 0;
|
||||
total = list.stream().mapToInt(ISearchProvider::total).sum();
|
||||
|
||||
@@ -7,12 +7,14 @@ import java.awt.FlowLayout;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Box;
|
||||
@@ -62,6 +64,7 @@ import jadx.gui.utils.JumpPosition;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.TextStandardActions;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.cache.ValueCache;
|
||||
import jadx.gui.utils.layout.WrapLayout;
|
||||
import jadx.gui.utils.rx.RxUtils;
|
||||
|
||||
@@ -160,6 +163,10 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
*/
|
||||
private final Executor searchBackgroundExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
// save values between searches
|
||||
private final ValueCache<String, List<JavaClass>> includedClsCache = new ValueCache<>();
|
||||
private final ValueCache<List<JavaClass>, List<List<JavaClass>>> batchesCache = new ValueCache<>();
|
||||
|
||||
private SearchDialog(MainWindow mainWindow, SearchPreset preset, Set<SearchOptions> additionalOptions) {
|
||||
super(mainWindow, NLS.str("menu.text_search"));
|
||||
this.searchPreset = preset;
|
||||
@@ -452,37 +459,16 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
if (text == null || options.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// allow empty text for comments search
|
||||
// allow empty text for search in comments
|
||||
if (text.isEmpty() && !options.contains(SearchOptions.COMMENT)) {
|
||||
return null;
|
||||
}
|
||||
LOG.debug("Building search for '{}', options: {}", text, options);
|
||||
boolean ignoreCase = options.contains(IGNORE_CASE);
|
||||
boolean useRegex = options.contains(USE_REGEX);
|
||||
|
||||
// Find the JavaPackage for the searched package string
|
||||
String packageText = packageField.getText();
|
||||
JavaPackage searchPackage = null;
|
||||
if (!packageText.isBlank()) {
|
||||
searchPackage = mainWindow
|
||||
.getWrapper()
|
||||
.getPackages()
|
||||
.stream()
|
||||
.filter(p -> p.getFullName().equals(packageText))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (searchPackage == null) {
|
||||
resultsInfoLabel.setText(NLS.str("search_dialog.package_not_found"));
|
||||
packageField.setBackground(SEARCH_FIELD_ERROR_COLOR);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (Objects.equals(packageField.getBackground(), SEARCH_FIELD_ERROR_COLOR)) {
|
||||
packageField.setBackground(searchFieldDefaultBgColor);
|
||||
}
|
||||
|
||||
SearchSettings searchSettings = new SearchSettings(text, ignoreCase, useRegex, searchPackage);
|
||||
String error = searchSettings.prepare();
|
||||
String searchPackageText = packageField.getText();
|
||||
SearchSettings searchSettings = new SearchSettings(text, ignoreCase, useRegex, searchPackageText);
|
||||
String error = searchSettings.prepare(mainWindow);
|
||||
if (error == null) {
|
||||
if (Objects.equals(searchField.getBackground(), SEARCH_FIELD_ERROR_COLOR)) {
|
||||
searchField.setBackground(searchFieldDefaultBgColor);
|
||||
@@ -500,7 +486,7 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
}
|
||||
|
||||
private boolean buildSearch(SearchTask newSearchTask, String text, SearchSettings searchSettings) {
|
||||
List<JavaClass> allClasses;
|
||||
List<JavaClass> searchClasses;
|
||||
if (options.contains(ACTIVE_TAB)) {
|
||||
JumpPosition currentPos = mainWindow.getTabbedPane().getCurrentPosition();
|
||||
if (currentPos == null) {
|
||||
@@ -511,57 +497,67 @@ public class SearchDialog extends CommonSearchDialog {
|
||||
if (currentNode instanceof JClass) {
|
||||
JClass activeCls = currentNode.getRootClass();
|
||||
searchSettings.setActiveCls(activeCls);
|
||||
allClasses = Collections.singletonList(activeCls.getCls());
|
||||
searchClasses = Collections.singletonList(activeCls.getCls());
|
||||
} else if (currentNode instanceof JResource) {
|
||||
searchSettings.setActiveResource((JResource) currentNode);
|
||||
allClasses = Collections.emptyList();
|
||||
searchClasses = Collections.emptyList();
|
||||
} else {
|
||||
resultsInfoLabel.setText("Can't search in current tab");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
allClasses = mainWindow.getWrapper().getIncludedClassesWithInners();
|
||||
searchClasses = includedClsCache.get(mainWindow.getSettings().getExcludedPackages(),
|
||||
exc -> mainWindow.getWrapper().getIncludedClassesWithInners());
|
||||
}
|
||||
JavaPackage searchPkg = searchSettings.getSearchPackage();
|
||||
if (searchPkg != null) {
|
||||
searchClasses = searchClasses.stream()
|
||||
.filter(searchSettings::isInSearchPkg)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
// allow empty text for comments search
|
||||
if (text.isEmpty() && options.contains(SearchOptions.COMMENT)) {
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
// allow empty text for comment search
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings, searchClasses));
|
||||
return true;
|
||||
}
|
||||
// using ordered execution for fast tasks
|
||||
MergedSearchProvider merged = new MergedSearchProvider();
|
||||
if (options.contains(CLASS)) {
|
||||
merged.add(new ClassSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
}
|
||||
if (options.contains(METHOD)) {
|
||||
merged.add(new MethodSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
}
|
||||
if (options.contains(FIELD)) {
|
||||
merged.add(new FieldSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
}
|
||||
if (options.contains(CODE)) {
|
||||
int clsCount = allClasses.size();
|
||||
if (clsCount == 1) {
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, allClasses));
|
||||
} else if (clsCount > 1) {
|
||||
List<List<JavaClass>> batches = mainWindow.getCacheObject().getDecompileBatches();
|
||||
if (batches == null) {
|
||||
List<JavaClass> topClasses = ListUtils.filter(allClasses, c -> !c.isInner());
|
||||
batches = mainWindow.getWrapper().buildDecompileBatches(topClasses);
|
||||
mainWindow.getCacheObject().setDecompileBatches(batches);
|
||||
}
|
||||
for (List<JavaClass> batch : batches) {
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, batch));
|
||||
if (!searchClasses.isEmpty()) {
|
||||
// using ordered execution for fast tasks
|
||||
MergedSearchProvider merged = new MergedSearchProvider();
|
||||
if (options.contains(CLASS)) {
|
||||
merged.add(new ClassSearchProvider(mainWindow, searchSettings, searchClasses));
|
||||
}
|
||||
if (options.contains(METHOD)) {
|
||||
merged.add(new MethodSearchProvider(mainWindow, searchSettings, searchClasses));
|
||||
}
|
||||
if (options.contains(FIELD)) {
|
||||
merged.add(new FieldSearchProvider(mainWindow, searchSettings, searchClasses));
|
||||
}
|
||||
if (!merged.isEmpty()) {
|
||||
merged.prepare();
|
||||
newSearchTask.addProviderJob(merged);
|
||||
}
|
||||
|
||||
if (options.contains(CODE)) {
|
||||
int clsCount = searchClasses.size();
|
||||
if (clsCount == 1) {
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, searchClasses, null));
|
||||
} else if (clsCount > 1) {
|
||||
List<JavaClass> topClasses = ListUtils.filter(searchClasses, c -> !c.isInner());
|
||||
List<List<JavaClass>> batches = batchesCache.get(topClasses,
|
||||
clsList -> mainWindow.getWrapper().buildDecompileBatches(clsList));
|
||||
Set<JavaClass> includedClasses = new HashSet<>(topClasses);
|
||||
for (List<JavaClass> batch : batches) {
|
||||
newSearchTask.addProviderJob(new CodeSearchProvider(mainWindow, searchSettings, batch, includedClasses));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options.contains(COMMENT)) {
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings, searchClasses));
|
||||
}
|
||||
}
|
||||
if (options.contains(RESOURCE)) {
|
||||
newSearchTask.addProviderJob(new ResourceSearchProvider(mainWindow, searchSettings, this));
|
||||
}
|
||||
if (options.contains(COMMENT)) {
|
||||
newSearchTask.addProviderJob(new CommentSearchProvider(mainWindow, searchSettings));
|
||||
}
|
||||
merged.prepare();
|
||||
newSearchTask.addProviderJob(merged);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package jadx.gui.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.ui.dialog.SearchDialog;
|
||||
import jadx.gui.utils.pkgs.PackageHelper;
|
||||
@@ -21,8 +19,6 @@ public class CacheObject {
|
||||
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
|
||||
private String lastSearchPackage;
|
||||
|
||||
private List<List<JavaClass>> decompileBatches;
|
||||
|
||||
private volatile boolean fullDecompilationFinished;
|
||||
|
||||
public CacheObject(JadxWrapper wrapper) {
|
||||
@@ -37,7 +33,6 @@ public class CacheObject {
|
||||
jNodeCache.reset();
|
||||
lastSearchOptions = new HashMap<>();
|
||||
lastSearchPackage = null;
|
||||
decompileBatches = null;
|
||||
fullDecompilationFinished = false;
|
||||
}
|
||||
|
||||
@@ -67,14 +62,6 @@ public class CacheObject {
|
||||
return lastSearchOptions;
|
||||
}
|
||||
|
||||
public @Nullable List<List<JavaClass>> getDecompileBatches() {
|
||||
return decompileBatches;
|
||||
}
|
||||
|
||||
public void setDecompileBatches(List<List<JavaClass>> decompileBatches) {
|
||||
this.decompileBatches = decompileBatches;
|
||||
}
|
||||
|
||||
public PackageHelper getPackageHelper() {
|
||||
return packageHelper;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package jadx.gui.utils.cache;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Simple store for values depending on 'key' object.
|
||||
*
|
||||
* @param <K> key object type
|
||||
* @param <V> stored object type
|
||||
*/
|
||||
public class ValueCache<K, V> {
|
||||
private K key;
|
||||
private V value;
|
||||
|
||||
/**
|
||||
* Return a stored object if key not changed, load a new object overwise.
|
||||
*/
|
||||
public synchronized V get(K requestKey, Function<K, V> loadFunc) {
|
||||
if (key != null && key.equals(requestKey)) {
|
||||
return value;
|
||||
}
|
||||
V newValue = loadFunc.apply(requestKey);
|
||||
key = requestKey;
|
||||
value = newValue;
|
||||
return newValue;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user