diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index 1043486c34c4a8f6800f7065eaeef3f269f7d3d3..0d1ac8cb2f97ce705938a2ede08cc64f20c3f6bd 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -758,6 +758,10 @@ public class ClassNode extends NotificationAttrNode implements ILoadable, ICodeN this.codegenDeps = codegenDeps; } + public int getTotalDepsCount() { + return dependencies.size() + codegenDeps.size(); + } + public List getUseIn() { return useIn; } diff --git a/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java b/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java index ef2ee6fdf347316411fd3f16beec98ec708ead0b..11ab0c5a8f3f4464836e56c3dec94607cd533c33 100644 --- a/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java +++ b/jadx-core/src/main/java/jadx/core/utils/DecompilerScheduler.java @@ -3,11 +3,10 @@ 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 java.util.stream.Collectors; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -33,16 +32,21 @@ public class DecompilerScheduler implements IDecompileScheduler { @Override public List> buildBatches(List classes) { - long start = System.currentTimeMillis(); - List> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode)); - List> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode)); - if (LOG.isDebugEnabled()) { - LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start); - } - if (DEBUG_BATCHES) { - check(result, classes); + try { + long start = System.currentTimeMillis(); + List> batches = internalBatches(Utils.collectionMap(classes, JavaClass::getClassNode)); + List> result = Utils.collectionMap(batches, l -> Utils.collectionMapNoNull(l, decompiler::getJavaClassByNode)); + if (LOG.isDebugEnabled()) { + LOG.debug("Build decompilation batches in {}ms", System.currentTimeMillis() - start); + } + if (DEBUG_BATCHES) { + check(result, classes); + } + return result; + } catch (Throwable e) { + LOG.warn("Build batches failed (continue with fallback)", e); + return buildFallback(classes); } - return result; } /** @@ -50,17 +54,9 @@ public class DecompilerScheduler implements IDecompileScheduler { * Build batches for dependencies of single class to avoid locking from another thread. */ public List> internalBatches(List classes) { - Map depsMap = new HashMap<>(classes.size()); - Set visited = new HashSet<>(); - for (ClassNode classNode : classes) { - visited.clear(); - sumDeps(classNode, depsMap, visited); - } - List deps = new ArrayList<>(depsMap.values()); - Collections.sort(deps); - + List deps = sumDependencies(classes); Set added = new HashSet<>(classes.size()); - Comparator cmpDepSize = Comparator.comparingInt(c -> c.getDependencies().size()); + Comparator cmpDepSize = Comparator.comparingInt(ClassNode::getTotalDepsCount); List> result = new ArrayList<>(); List mergedBatch = new ArrayList<>(MERGED_BATCH_SIZE); for (DepInfo depInfo : deps) { @@ -68,7 +64,7 @@ public class DecompilerScheduler implements IDecompileScheduler { if (!added.add(cls)) { continue; } - int depsSize = cls.getDependencies().size(); + int depsSize = cls.getTotalDepsCount(); if (depsSize == 0) { // add classes without dependencies in merged batch mergedBatch.add(cls); @@ -99,21 +95,17 @@ public class DecompilerScheduler implements IDecompileScheduler { return result; } - public int sumDeps(ClassNode cls, Map depsMap, Set visited) { - visited.add(cls); - DepInfo depInfo = depsMap.get(cls); - if (depInfo != null) { - return depInfo.getDepsCount(); - } - List deps = cls.getDependencies(); - int count = deps.size(); - for (ClassNode dep : deps) { - if (!visited.contains(dep)) { - count += sumDeps(dep, depsMap, visited); + private static List sumDependencies(List classes) { + List deps = new ArrayList<>(classes.size()); + for (ClassNode cls : classes) { + int count = 0; + for (ClassNode dep : cls.getDependencies()) { + count += 1 + dep.getTotalDepsCount(); } + deps.add(new DepInfo(cls, count)); } - depsMap.put(cls, new DepInfo(cls, count)); - return count; + Collections.sort(deps); + return deps; } private static final class DepInfo implements Comparable { @@ -135,20 +127,36 @@ public class DecompilerScheduler implements IDecompileScheduler { @Override public int compareTo(@NotNull DecompilerScheduler.DepInfo o) { - return Integer.compare(depsCount, o.depsCount); + int deps = Integer.compare(depsCount, o.depsCount); + if (deps == 0) { + return cls.compareTo(o.cls); + } + return deps; + } + + @Override + public String toString() { + return cls + ":" + depsCount; } } + private static List> buildFallback(List classes) { + return classes.stream() + .sorted(Comparator.comparingInt(c -> c.getClassNode().getTotalDepsCount())) + .map(Collections::singletonList) + .collect(Collectors.toList()); + } + private void dumpBatchesStats(List classes, List> result, List 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); + int maxSingleDeps = classes.stream().mapToInt(ClassNode::getTotalDepsCount).max().orElse(-1); + int maxSubDeps = 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: " + String.format("%.2f", avg) + ",\n max single deps count: " + maxSingleDeps - + ",\n max recursive deps count: " + maxRecursiveDeps); + + ",\n max sub deps count: " + maxSubDeps); } private static void check(List> result, List classes) {