未验证 提交 5fc27c11 编写于 作者: S Skylot

perf(gui): improve decompilation speed (#1269)

- use index only in one thread to reduce synchronization locks
- collect usage info on request, remove global collection
- adjust decompilation order to reduce locks, improve memory usage
- prefill cache of super types in clsp graph to remove locks
上级 6bcc48c4
package jadx.api;
import java.util.List;
public interface IDecompileScheduler {
List<List<JavaClass>> buildBatches(List<JavaClass> classes);
}
......@@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -38,6 +39,7 @@ import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleProject;
import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.core.utils.files.FileUtils;
......@@ -91,6 +93,8 @@ public final class JadxDecompiler implements Closeable {
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler(this);
public JadxDecompiler() {
this(new JadxArgs());
}
......@@ -281,19 +285,26 @@ public final class JadxDecompiler implements Closeable {
private void appendSourcesSave(List<Runnable> tasks, File outDir) {
Predicate<String> classFilter = args.getClassFilter();
for (JavaClass cls : getClasses()) {
List<JavaClass> classes = getClasses();
List<JavaClass> processQueue = new ArrayList<>(classes.size());
for (JavaClass cls : classes) {
if (cls.getClassNode().contains(AFlag.DONT_GENERATE)) {
continue;
}
if (classFilter != null && !classFilter.test(cls.getFullName())) {
continue;
}
processQueue.add(cls);
}
for (List<JavaClass> decompileBatch : decompileScheduler.buildBatches(processQueue)) {
tasks.add(() -> {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls.getFullName(), e);
for (JavaClass cls : decompileBatch) {
try {
ICodeInfo code = cls.getCodeInfo();
SaveCode.save(outDir, cls.getClassNode(), code);
} catch (Exception e) {
LOG.error("Error saving class: {}", cls, e);
}
}
});
}
......@@ -405,7 +416,8 @@ public final class JadxDecompiler implements Closeable {
}
@Nullable("For not generated classes")
private JavaClass getJavaClassByNode(ClassNode cls) {
@ApiStatus.Internal
public JavaClass getJavaClassByNode(ClassNode cls) {
JavaClass javaClass = classesMap.get(cls);
if (javaClass != null) {
return javaClass;
......@@ -554,6 +566,23 @@ public final class JadxDecompiler implements Closeable {
throw new JadxRuntimeException("Unexpected node type: " + obj);
}
// TODO: make interface for all nodes in code annotations and add common method instead this
Object getInternalNode(JavaNode javaNode) {
if (javaNode instanceof JavaClass) {
return ((JavaClass) javaNode).getClassNode();
}
if (javaNode instanceof JavaMethod) {
return ((JavaMethod) javaNode).getMethodNode();
}
if (javaNode instanceof JavaField) {
return ((JavaField) javaNode).getFieldNode();
}
if (javaNode instanceof JavaVariable) {
return ((JavaVariable) javaNode).getVarRef();
}
throw new JadxRuntimeException("Unexpected node type: " + javaNode);
}
List<JavaNode> convertNodes(Collection<?> nodesList) {
return nodesList.stream()
.map(this::convertNode)
......@@ -597,6 +626,10 @@ public final class JadxDecompiler implements Closeable {
return pluginManager;
}
public IDecompileScheduler getDecompileScheduler() {
return decompileScheduler;
}
@Override
public String toString() {
return "jadx decompiler " + getVersion();
......
......@@ -7,6 +7,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.core.dex.attributes.AFlag;
......@@ -69,6 +70,7 @@ public final class JavaClass implements JavaNode {
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public ClassNode getClassNode() {
return cls;
}
......@@ -155,6 +157,23 @@ public final class JavaClass implements JavaNode {
return resultMap;
}
public List<CodePosition> getUsageFor(JavaNode javaNode) {
Map<CodePosition, Object> map = getCodeAnnotations();
if (map.isEmpty() || decompiler == null) {
return Collections.emptyList();
}
Object internalNode = getRootDecompiler().getInternalNode(javaNode);
List<CodePosition> result = new ArrayList<>();
for (Map.Entry<CodePosition, Object> entry : map.entrySet()) {
CodePosition codePosition = entry.getKey();
Object obj = entry.getValue();
if (internalNode.equals(obj)) {
result.add(codePosition);
}
}
return result;
}
@Override
public List<JavaNode> getUseIn() {
return getRootDecompiler().convertNodes(cls.getUseIn());
......
......@@ -2,6 +2,8 @@ package jadx.api;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
......@@ -67,6 +69,7 @@ public final class JavaField implements JavaNode {
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public FieldNode getFieldNode() {
return field;
}
......
......@@ -4,6 +4,8 @@ import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.jetbrains.annotations.ApiStatus;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
......@@ -101,6 +103,7 @@ public final class JavaMethod implements JavaNode {
/**
* Internal API. Not Stable!
*/
@ApiStatus.Internal
public MethodNode getMethodNode() {
return mth;
}
......
......@@ -3,6 +3,8 @@ package jadx.api;
import java.util.Collections;
import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.data.annotations.VarDeclareRef;
import jadx.api.data.annotations.VarRef;
......@@ -32,6 +34,11 @@ public class JavaVariable implements JavaNode {
return varRef.getName();
}
@ApiStatus.Internal
public VarRef getVarRef() {
return varRef;
}
@Override
public String getFullName() {
return varRef.getType() + " " + varRef.getName() + " (r" + varRef.getReg() + "v" + varRef.getSsa() + ")";
......
......@@ -8,9 +8,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -30,7 +28,7 @@ public class ClspGraph {
private static final Logger LOG = LoggerFactory.getLogger(ClspGraph.class);
private final RootNode root;
private final Map<String, Set<String>> superTypesCache = Collections.synchronizedMap(new WeakHashMap<>());
private Map<String, Set<String>> superTypesCache;
private Map<String, ClspClass> nameMap;
private final Set<String> missingClasses = new HashSet<>();
......@@ -159,9 +157,12 @@ public class ClspGraph {
}
public Set<String> getSuperTypes(String clsName) {
Set<String> fromCache = superTypesCache.get(clsName);
if (fromCache != null) {
return fromCache;
if (superTypesCache != null) {
Set<String> result = superTypesCache.get(clsName);
if (result == null) {
return Collections.emptySet();
}
return result;
}
ClspClass cls = nameMap.get(clsName);
if (cls == null) {
......@@ -170,18 +171,25 @@ public class ClspGraph {
}
Set<String> result = new HashSet<>();
addSuperTypes(cls, result);
return putInSuperTypesCache(clsName, result);
return result;
}
@NotNull
private Set<String> putInSuperTypesCache(String clsName, Set<String> result) {
if (result.isEmpty()) {
Set<String> empty = Collections.emptySet();
superTypesCache.put(clsName, empty);
return empty;
public synchronized void fillSuperTypesCache() {
Map<String, Set<String>> map = new HashMap<>(nameMap.size());
Set<String> tmpSet = new HashSet<>();
for (Map.Entry<String, ClspClass> entry : nameMap.entrySet()) {
ClspClass cls = entry.getValue();
tmpSet.clear();
addSuperTypes(cls, tmpSet);
Set<String> result;
if (tmpSet.isEmpty()) {
result = Collections.emptySet();
} else {
result = new HashSet<>(tmpSet);
}
map.put(cls.getName(), result);
}
superTypesCache.put(clsName, result);
return result;
superTypesCache = map;
}
private void addSuperTypes(ClspClass cls, Set<String> result) {
......@@ -203,9 +211,7 @@ public class ClspGraph {
private ClspClass getClspClass(ArgType clsType) {
ClspClass clspClass = nameMap.get(clsType.getObject());
if (clspClass == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("External class not found: {}", clsType.getObject());
}
missingClasses.add(clsType.getObject());
}
return clspClass;
}
......
......@@ -682,7 +682,12 @@ public class InsnGen {
code.add("this");
} else {
code.add("new ");
code.attachAnnotation(callMth);
if (callMth == null || callMth.contains(AFlag.DONT_GENERATE)) {
// use class reference if constructor method is missing (default constructor)
code.attachAnnotation(mth.root().resolveClass(insn.getCallMth().getDeclClass()));
} else {
code.attachAnnotation(callMth);
}
mgen.getClassGen().addClsName(code, insn.getClassType());
GenericInfoAttr genericInfoAttr = insn.get(AType.GENERIC_INFO);
if (genericInfoAttr != null) {
......
......@@ -43,6 +43,11 @@ import jadx.core.utils.exceptions.JadxException;
)
public class OverrideMethodVisitor extends AbstractVisitor {
@Override
public void init(RootNode root) throws JadxException {
root.getClsp().fillSuperTypesCache();
}
@Override
public boolean visit(ClassNode cls) throws JadxException {
processCls(cls);
......
package jadx.core.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.IDecompileScheduler;
import jadx.api.JadxDecompiler;
import jadx.api.JavaClass;
import jadx.core.dex.nodes.ClassNode;
public class DecompilerScheduler implements IDecompileScheduler {
private static final Logger LOG = LoggerFactory.getLogger(DecompilerScheduler.class);
private static final int MERGED_BATCH_SIZE = 16;
private static final boolean DUMP_STATS = false;
private final JadxDecompiler decompiler;
public DecompilerScheduler(JadxDecompiler decompiler) {
this.decompiler = decompiler;
}
@Override
public List<List<JavaClass>> buildBatches(List<JavaClass> classes) {
long start = System.currentTimeMillis();
List<List<ClassNode>> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode));
List<List<JavaClass>> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode));
if (LOG.isDebugEnabled()) {
LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start);
}
return result;
}
/**
* Put classes with many dependencies at the end.
* Build batches for dependencies of single class to avoid locking from another thread.
*/
public List<List<ClassNode>> internalBatches(List<ClassNode> classes) {
Map<ClassNode, DepInfo> depsMap = new HashMap<>(classes.size());
Set<ClassNode> visited = new HashSet<>();
for (ClassNode classNode : classes) {
visited.clear();
sumDeps(classNode, depsMap, visited);
}
List<DepInfo> deps = new ArrayList<>(depsMap.values());
Collections.sort(deps);
Set<ClassNode> added = new HashSet<>(classes.size());
Comparator<ClassNode> cmpDepSize = Comparator.comparingInt(c -> c.getDependencies().size());
List<List<ClassNode>> result = new ArrayList<>();
List<ClassNode> mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
for (DepInfo depInfo : deps) {
ClassNode cls = depInfo.getCls();
int depsSize = cls.getDependencies().size();
if (depsSize == 0) {
// add classes without dependencies in merged batch
mergedBatch.add(cls);
added.add(cls);
if (mergedBatch.size() >= MERGED_BATCH_SIZE) {
result.add(mergedBatch);
mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE);
}
} else {
List<ClassNode> batch = new ArrayList<>(depsSize + 1);
for (ClassNode dep : cls.getDependencies()) {
ClassNode topDep = dep.getTopParentClass();
if (!added.contains(topDep)) {
batch.add(topDep);
}
}
batch.sort(cmpDepSize);
batch.add(cls);
added.addAll(batch);
result.add(batch);
}
}
if (mergedBatch.size() > 0) {
result.add(mergedBatch);
}
if (DUMP_STATS) {
dumpBatchesStats(classes, result, deps);
}
return result;
}
public int sumDeps(ClassNode cls, Map<ClassNode, DepInfo> depsMap, Set<ClassNode> visited) {
visited.add(cls);
DepInfo depInfo = depsMap.get(cls);
if (depInfo != null) {
return depInfo.getDepsCount();
}
List<ClassNode> deps = cls.getDependencies();
int count = deps.size();
for (ClassNode dep : deps) {
if (!visited.contains(dep)) {
count += sumDeps(dep, depsMap, visited);
}
}
depsMap.put(cls, new DepInfo(cls, count));
return count;
}
private static final class DepInfo implements Comparable<DepInfo> {
private final ClassNode cls;
private final int depsCount;
private DepInfo(ClassNode cls, int depsCount) {
this.cls = cls;
this.depsCount = depsCount;
}
public ClassNode getCls() {
return cls;
}
public int getDepsCount() {
return depsCount;
}
@Override
public int compareTo(@NotNull DecompilerScheduler.DepInfo o) {
return Integer.compare(depsCount, o.depsCount);
}
}
private void dumpBatchesStats(List<ClassNode> classes, List<List<ClassNode>> result, List<DepInfo> deps) {
double avg = result.stream().mapToInt(List::size).average().orElse(-1);
int maxSingleDeps = classes.stream().mapToInt(c -> c.getDependencies().size()).max().orElse(-1);
int maxRecursiveDeps = deps.stream().mapToInt(DepInfo::getDepsCount).max().orElse(-1);
LOG.info("Batches stats:"
+ "\n input classes: " + classes.size()
+ ",\n batches: " + result.size()
+ ",\n average batch size: " + avg
+ ",\n max single deps count: " + maxSingleDeps
+ ",\n max recursive deps count: " + maxRecursiveDeps);
}
}
......@@ -207,6 +207,20 @@ public class Utils {
return result;
}
public static <T, R> List<R> collectionMapNoNull(Collection<T> list, Function<T, R> mapFunc) {
if (list == null || list.isEmpty()) {
return Collections.emptyList();
}
List<R> result = new ArrayList<>(list.size());
for (T t : list) {
R r = mapFunc.apply(t);
if (r != null) {
result.add(r);
}
}
return result;
}
public static <T> boolean containsInListByRef(List<T> list, T element) {
if (isEmpty(list)) {
return false;
......
......@@ -88,6 +88,10 @@ public class JadxWrapper {
}).collect(Collectors.toList());
}
public List<List<JavaClass>> buildDecompileBatches(List<JavaClass> classes) {
return decompiler.getDecompileScheduler().buildBatches(classes);
}
// TODO: move to CLI and filter classes in JadxDecompiler
public List<String> getExcludedPackages() {
String excludedPackages = settings.getExcludedPackages().trim();
......
......@@ -119,6 +119,7 @@ public class BackgroundExecutor {
private TaskStatus waitTermination(ThreadPoolExecutor executor) throws InterruptedException {
Supplier<TaskStatus> cancelCheck = buildCancelCheck();
try {
int k = 0;
while (true) {
if (executor.isTerminated()) {
return TaskStatus.COMPLETE;
......@@ -129,7 +130,8 @@ public class BackgroundExecutor {
return cancelStatus;
}
setProgress(calcProgress(executor.getCompletedTaskCount()));
Thread.sleep(300);
k++;
Thread.sleep(k < 20 ? 100 : 1000); // faster update for short tasks
}
} catch (InterruptedException e) {
LOG.debug("Task wait interrupted");
......
......@@ -54,14 +54,30 @@ public class DecompileTask implements IBackgroundTask {
complete.set(0);
List<Runnable> jobs = new ArrayList<>(expectedCompleteCount + 1);
for (JavaClass cls : classesForIndex) {
jobs.add(indexService::indexResources);
for (List<JavaClass> batch : wrapper.buildDecompileBatches(classesForIndex)) {
jobs.add(() -> {
cls.decompile();
indexService.indexCls(cls);
complete.incrementAndGet();
for (JavaClass cls : batch) {
try {
cls.decompile();
} catch (Throwable e) {
LOG.error("Failed to decompile class: {}", cls, e);
} finally {
complete.incrementAndGet();
}
}
});
}
jobs.add(indexService::indexResources);
jobs.add(() -> {
for (JavaClass cls : classesForIndex) {
try {
// TODO: a lot of synchronizations to index object, not effective for parallel usage
indexService.indexCls(cls);
} catch (Throwable e) {
LOG.error("Failed to index class: {}", cls, e);
}
}
});
startTime = System.currentTimeMillis();
return jobs;
}
......
......@@ -12,7 +12,6 @@ import jadx.api.ICodeWriter;
import jadx.api.JavaClass;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.search.StringRef;
import jadx.gui.utils.search.TextSearchIndex;
......@@ -28,21 +27,19 @@ public class IndexService {
this.cache = cache;
}
/**
* Warning! Not ready for parallel execution. Use only in a single thread.
*/
public void indexCls(JavaClass cls) {
try {
TextSearchIndex index = cache.getTextIndex();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (index == null || usageInfo == null) {
if (index == null) {
return;
}
index.indexNames(cls);
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
List<StringRef> lines = splitLines(cls);
usageInfo.processClass(cls, linesInfo, lines);
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
index.indexCode(cls, linesInfo, lines);
index.indexNames(cls);
indexSet.add(cls);
} catch (Exception e) {
LOG.error("Index error in class: {}", cls.getFullName(), e);
......@@ -54,15 +51,13 @@ public class IndexService {
index.indexResource();
}
public void refreshIndex(JavaClass cls) {
public synchronized void refreshIndex(JavaClass cls) {
TextSearchIndex index = cache.getTextIndex();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (index == null || usageInfo == null) {
if (index == null) {
return;
}
indexSet.remove(cls);
index.remove(cls);
usageInfo.remove(cls);
indexCls(cls);
}
......
......@@ -122,7 +122,6 @@ import jadx.gui.update.JadxUpdate;
import jadx.gui.update.JadxUpdate.IUpdateCallback;
import jadx.gui.update.data.Release;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.FontUtils;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.LafManager;
......@@ -525,7 +524,6 @@ public class MainWindow extends JFrame {
cacheObject.setJadxSettings(settings);
cacheObject.setIndexService(new IndexService(cacheObject));
cacheObject.setUsageInfo(new CodeUsageInfo(cacheObject.getNodeCache()));
cacheObject.setTextIndex(new TextSearchIndex(this));
}
......
......@@ -53,6 +53,7 @@ import jadx.gui.ui.TabbedPane;
import jadx.gui.ui.codearea.AbstractCodeArea;
import jadx.gui.ui.panel.ProgressPanel;
import jadx.gui.utils.CacheObject;
import jadx.gui.utils.JNodeCache;
import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
......@@ -564,4 +565,8 @@ public abstract class CommonSearchDialog extends JDialog {
warnLabel.setVisible(true);
}
}
protected JNodeCache getNodeCache() {
return mainWindow.getCacheObject().getNodeCache();
}
}
......@@ -3,10 +3,10 @@ package jadx.gui.ui.dialog;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.BorderFactory;
import javax.swing.JLabel;
......@@ -14,23 +14,26 @@ import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import jadx.api.CodePosition;
import jadx.api.ICodeWriter;
import jadx.api.JavaClass;
import jadx.api.JavaNode;
import jadx.gui.jobs.IndexService;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.CodeUsageInfo;
import jadx.gui.utils.CodeLinesInfo;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.search.StringRef;
public class UsageDialog extends CommonSearchDialog {
private static final long serialVersionUID = -5105405789969134105L;
private final transient JNode node;
private transient List<CodeNode> usageList;
public UsageDialog(MainWindow mainWindow, JNode node) {
super(mainWindow);
this.node = node;
......@@ -42,32 +45,9 @@ public class UsageDialog extends CommonSearchDialog {
@Override
protected void openInit() {
IndexService indexService = mainWindow.getCacheObject().getIndexService();
if (indexService.isComplete()) {
loadFinishedCommon();
loadFinished();
return;
}
List<JavaNode> useIn = node.getJavaNode().getUseIn();
List<JavaClass> usageTopClsForIndex = useIn
.stream()
.map(JavaNode::getTopParentClass)
.filter(indexService::isIndexNeeded)
.distinct()
.sorted(Comparator.comparing(JavaClass::getFullName))
.collect(Collectors.toList());
if (usageTopClsForIndex.isEmpty()) {
loadFinishedCommon();
loadFinished();
return;
}
usageList = new ArrayList<>();
mainWindow.getBackgroundExecutor().execute(NLS.str("progress.load"),
() -> {
for (JavaClass cls : usageTopClsForIndex) {
cls.decompile();
indexService.indexCls(cls);
}
},
this::collectUsageData,
(status) -> {
if (status == TaskStatus.CANCEL_BY_MEMORY) {
mainWindow.showHeapUsageBar();
......@@ -78,6 +58,45 @@ public class UsageDialog extends CommonSearchDialog {
});
}
private void collectUsageData() {
node.getJavaNode().getUseIn()
.stream()
.map(JavaNode::getTopParentClass)
.distinct()
.sorted(Comparator.comparing(JavaClass::getFullName))
.forEach(this::processUsageClass);
}
private void processUsageClass(JavaClass cls) {
String code = cls.getCodeInfo().getCodeStr();
CodeLinesInfo linesInfo = new CodeLinesInfo(cls);
JavaNode javaNode = node.getJavaNode();
List<CodePosition> usage = cls.getUsageFor(javaNode);
for (CodePosition pos : usage) {
if (javaNode.getTopParentClass().equals(cls) && pos.getPos() == javaNode.getDefPos()) {
// skip declaration
continue;
}
StringRef line = getLineStrAt(code, pos.getPos());
if (line.startsWith("import ")) {
continue;
}
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(pos.getLine());
JNode useAtNode = javaNodeByLine == null ? node : getNodeCache().makeFrom(javaNodeByLine);
usageList.add(new CodeNode(useAtNode, line, pos.getLine(), pos.getPos()));
}
}
private StringRef getLineStrAt(String code, int pos) {
String newLine = ICodeWriter.NL;
int start = code.lastIndexOf(newLine, pos);
int end = code.indexOf(newLine, pos);
if (start == -1 || end == -1) {
return StringRef.fromStr("line not found");
}
return StringRef.subString(code, start + newLine.length(), end).trim();
}
@Override
protected void loadFinished() {
resultsTable.setEnabled(true);
......@@ -92,12 +111,6 @@ public class UsageDialog extends CommonSearchDialog {
@Override
protected synchronized void performSearch() {
resultsModel.clear();
CodeUsageInfo usageInfo = cache.getUsageInfo();
if (usageInfo == null) {
return;
}
List<CodeNode> usageList = usageInfo.getUsageList(node);
Collections.sort(usageList);
resultsModel.addAll(usageList);
// TODO: highlight only needed node usage
......
......@@ -18,7 +18,6 @@ public class CacheObject {
private IndexService indexService;
private TextSearchIndex textIndex;
private CodeUsageInfo usageInfo;
private CommentsIndex commentsIndex;
private String lastSearch;
private JNodeCache jNodeCache;
......@@ -38,7 +37,6 @@ public class CacheObject {
textIndex = null;
lastSearch = null;
jNodeCache = new JNodeCache();
usageInfo = null;
lastSearchOptions = new HashMap<>();
}
......@@ -59,15 +57,6 @@ public class CacheObject {
this.lastSearch = lastSearch;
}
@Nullable
public CodeUsageInfo getUsageInfo() {
return usageInfo;
}
public void setUsageInfo(@Nullable CodeUsageInfo usageInfo) {
this.usageInfo = usageInfo;
}
public CommentsIndex getCommentsIndex() {
return commentsIndex;
}
......
package jadx.gui.utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.CodePosition;
import jadx.api.JavaClass;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.gui.treemodel.CodeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.search.StringRef;
public class CodeUsageInfo {
private static final Logger LOG = LoggerFactory.getLogger(CodeUsageInfo.class);
public static class UsageInfo {
private final List<CodeNode> usageList = new ArrayList<>();
public List<CodeNode> getUsageList() {
return usageList;
}
public synchronized void addUsage(CodeNode codeNode) {
usageList.add(codeNode);
}
public synchronized void removeUsageIf(Predicate<? super CodeNode> filter) {
usageList.removeIf(filter);
}
}
private final JNodeCache nodeCache;
public CodeUsageInfo(JNodeCache nodeCache) {
this.nodeCache = nodeCache;
}
private final Map<JNode, UsageInfo> usageMap = new ConcurrentHashMap<>();
public void processClass(JavaClass javaClass, CodeLinesInfo linesInfo, List<StringRef> lines) {
try {
Map<CodePosition, JavaNode> usage = javaClass.getUsageMap();
for (Map.Entry<CodePosition, JavaNode> entry : usage.entrySet()) {
CodePosition codePosition = entry.getKey();
JavaNode javaNode = entry.getValue();
if (javaNode.getTopParentClass().equals(javaClass)
&& codePosition.getPos() == javaNode.getDefPos()) {
// skip declaration
continue;
}
JNode node = nodeCache.makeFrom(javaNode);
addUsage(node, javaClass, linesInfo, codePosition, lines);
if (javaNode instanceof JavaMethod) {
// add constructor usage also as class usage
if (((JavaMethod) javaNode).isConstructor()) {
addUsage(node.getJParent(), javaClass, linesInfo, codePosition, lines);
}
}
}
} catch (Exception e) {
LOG.error("Code usage process failed for class: {}", javaClass, e);
}
}
private void addUsage(JNode jNode, JavaClass javaClass,
CodeLinesInfo linesInfo, CodePosition codePosition, List<StringRef> lines) {
int line = codePosition.getLine();
StringRef codeLine = lines.get(line - 1);
if (codeLine.startsWith("import ")) {
// skip imports
return;
}
JavaNode javaNodeByLine = linesInfo.getJavaNodeByLine(line);
JNode node = nodeCache.makeFrom(javaNodeByLine == null ? javaClass : javaNodeByLine);
CodeNode codeNode = new CodeNode(node, codeLine, line, codePosition.getPos());
UsageInfo usageInfo = usageMap.computeIfAbsent(jNode, key -> new UsageInfo());
usageInfo.addUsage(codeNode);
}
public List<CodeNode> getUsageList(JNode node) {
UsageInfo usageInfo = usageMap.get(node);
if (usageInfo == null) {
return Collections.emptyList();
}
return usageInfo.getUsageList();
}
public void remove(JavaClass cls) {
usageMap.entrySet().removeIf(e -> {
if (e.getKey().getJavaNode().getTopParentClass().equals(cls)) {
return true;
}
e.getValue().removeUsageIf(node -> node.getJavaNode().getTopParentClass().equals(cls));
return false;
});
}
}
......@@ -23,6 +23,7 @@ public class JNodeCache {
if (javaNode == null) {
return null;
}
// don't use 'computeIfAbsent' method here, it this cause 'Recursive update' exception
JNode jNode = cache.get(javaNode);
if (jNode == null) {
jNode = convert(javaNode);
......@@ -35,8 +36,20 @@ public class JNodeCache {
if (javaCls == null) {
return null;
}
return (JClass) cache.computeIfAbsent(javaCls,
jn -> new JClass(javaCls, makeFrom(javaCls.getDeclaringClass())));
JClass jCls = (JClass) cache.get(javaCls);
if (jCls == null) {
jCls = convert(javaCls);
cache.put(javaCls, jCls);
}
return jCls;
}
private JClass convert(JavaClass cls) {
JavaClass parentCls = cls.getDeclaringClass();
if (parentCls == cls) {
return new JClass(cls, null);
}
return new JClass(cls, makeFrom(parentCls));
}
private JNode convert(JavaNode node) {
......@@ -44,7 +57,7 @@ public class JNodeCache {
return null;
}
if (node instanceof JavaClass) {
return new JClass((JavaClass) node, makeFrom(node.getDeclaringClass()));
return convert(((JavaClass) node));
}
if (node instanceof JavaMethod) {
return new JMethod((JavaMethod) node, makeFrom(node.getDeclaringClass()));
......
......@@ -20,11 +20,11 @@ public class CodeIndex {
private final List<CodeNode> values = new ArrayList<>();
public synchronized void put(CodeNode value) {
public void put(CodeNode value) {
values.add(value);
}
public synchronized void removeForCls(JavaClass cls) {
public void removeForCls(JavaClass cls) {
values.removeIf(v -> v.getJavaNode().getTopParentClass().equals(cls));
}
......
package jadx.gui.utils.search;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
......@@ -12,7 +12,7 @@ import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JNode;
public class SimpleIndex {
private final Map<JNode, String> data = new ConcurrentHashMap<>();
private final Map<JNode, String> data = new HashMap<>();
public void put(String str, JNode value) {
data.put(value, str);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册