From cd38b03621928e64ddb449e4e63a511c0b371f94 Mon Sep 17 00:00:00 2001 From: Joseph Burton Date: Wed, 6 Aug 2025 14:32:45 +0100 Subject: Optimize JAR indexing (#552) * Multithread JAR indexing * Replace `Analyzer` with custom `AnalyzerAdapter` implementation * Compute frames on class files from Java 1.5 or older * Delete InterpreterPair * Don't analyze non-enum initializers in EnumFieldNameFindingVisitor * Classes may not have frames in java 6--- build.gradle | 11 + .../cuchaz/enigma/gui/dialog/ProgressDialog.java | 2 +- .../main/java/cuchaz/enigma/gui/util/GuiUtil.java | 4 +- enigma/src/main/java/cuchaz/enigma/Enigma.java | 4 +- .../enigma/analysis/BetterAnalyzerAdapter.java | 65 ++++++ .../java/cuchaz/enigma/analysis/BuiltinPlugin.java | 13 +- .../cuchaz/enigma/analysis/IndexClassWriter.java | 133 ++++++++++++ .../enigma/analysis/IndexSimpleVerifier.java | 160 --------------- .../cuchaz/enigma/analysis/InterpreterPair.java | 106 ---------- .../enigma/analysis/MethodNodeWithAction.java | 19 -- .../cuchaz/enigma/analysis/StructureTreeNode.java | 2 +- .../enigma/analysis/index/BridgeMethodIndex.java | 16 +- .../cuchaz/enigma/analysis/index/EntryIndex.java | 12 +- .../analysis/index/IndexReferenceVisitor.java | 224 ++++++++++----------- .../enigma/analysis/index/InheritanceIndex.java | 21 +- .../cuchaz/enigma/analysis/index/JarIndex.java | 51 +++-- .../analysis/index/PackageVisibilityIndex.java | 29 ++- .../enigma/analysis/index/ReferenceIndex.java | 72 +++---- .../enigma/api/service/JarIndexerService.java | 21 ++ .../AddFramesIfNecessaryClassProvider.java | 49 +++++ .../mapping/serde/MappingIoConverter.java | 6 +- .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 4 +- 22 files changed, 517 insertions(+), 507 deletions(-) create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/BetterAnalyzerAdapter.java create mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/IndexClassWriter.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java create mode 100644 enigma/src/main/java/cuchaz/enigma/classprovider/AddFramesIfNecessaryClassProvider.java diff --git a/build.gradle b/build.gradle index 46794c5b..4453ab93 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,14 @@ plugins { id 'maven-publish' id 'com.github.johnrengelman.shadow' version '8.1.1' apply false + id 'com.diffplug.spotless' version '7.2.1' } subprojects { apply plugin: 'java' apply plugin: 'maven-publish' apply plugin: 'checkstyle' + apply plugin: "com.diffplug.spotless" repositories { mavenLocal() @@ -47,6 +49,15 @@ subprojects { toolVersion = '10.12.4' } + spotless { + lineEndings = com.diffplug.spotless.LineEnding.UNIX + + java { + removeUnusedImports() + importOrder('java', 'javax', '', 'cuchaz.enigma') + } + } + publishing { publications { "$project.name"(MavenPublication) { diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java index 3beae21c..19b85407 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java @@ -95,7 +95,7 @@ public class ProgressDialog implements ProgressListener, AutoCloseable { }, SwingUtilities::invokeLater).thenAcceptAsync(progress -> { try (progress) { runnable.run(progress); - } catch (Exception e) { + } catch (Throwable e) { CrashDialog.show(e); throw new RuntimeException(e); } diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java index 106859cc..da1a2474 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java @@ -5,15 +5,15 @@ import java.awt.Cursor; import java.awt.Desktop; import java.awt.Font; import java.awt.Toolkit; +import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.font.TextAttribute; import java.io.IOException; import java.net.URI; diff --git a/enigma/src/main/java/cuchaz/enigma/Enigma.java b/enigma/src/main/java/cuchaz/enigma/Enigma.java index 696a848d..414285d7 100644 --- a/enigma/src/main/java/cuchaz/enigma/Enigma.java +++ b/enigma/src/main/java/cuchaz/enigma/Enigma.java @@ -62,8 +62,8 @@ public class Enigma { Set scope = jarClassProvider.getClassNames(); JarIndex index = JarIndex.empty(); - index.indexJar(scope, classProvider, progress); - services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProvider, index)); + ClassProvider classProviderWithFrames = index.indexJar(scope, classProvider, progress); + services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProviderWithFrames, index)); return new EnigmaProject(this, path, classProvider, index, Utils.zipSha1(path)); } diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/BetterAnalyzerAdapter.java b/enigma/src/main/java/cuchaz/enigma/analysis/BetterAnalyzerAdapter.java new file mode 100644 index 00000000..976b2e63 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/BetterAnalyzerAdapter.java @@ -0,0 +1,65 @@ +package cuchaz.enigma.analysis; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.AnalyzerAdapter; + +/** + * An {@link AnalyzerAdapter} that works even if the class wasn't read with {@link ClassReader#EXPAND_FRAMES}. + */ +public class BetterAnalyzerAdapter extends AnalyzerAdapter { + private final List lastFrameLocals = new ArrayList<>(); + private final List lastFrameStack = new ArrayList<>(); + + protected BetterAnalyzerAdapter(int api, String owner, int access, String name, String descriptor, @Nullable MethodVisitor methodVisitor) { + super(api, owner, access, name, descriptor, methodVisitor); + + for (Object local : this.locals) { + if (!local.equals(Opcodes.TOP)) { + lastFrameLocals.add(local); + } + } + } + + @Override + public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) { + switch (type) { + case Opcodes.F_NEW -> { + super.visitFrame(type, numLocal, local, numStack, stack); + return; + } + case Opcodes.F_SAME -> { + lastFrameStack.clear(); + } + case Opcodes.F_SAME1 -> { + lastFrameStack.clear(); + lastFrameStack.add(stack[0]); + } + case Opcodes.F_APPEND -> { + Collections.addAll(lastFrameLocals, local); + lastFrameStack.clear(); + } + case Opcodes.F_CHOP -> { + lastFrameLocals.subList(lastFrameLocals.size() - numLocal, lastFrameLocals.size()).clear(); + lastFrameStack.clear(); + } + case Opcodes.F_FULL -> { + lastFrameLocals.clear(); + Collections.addAll(lastFrameLocals, local); + lastFrameStack.clear(); + Collections.addAll(lastFrameStack, stack); + } + default -> { + throw new AssertionError("Illegal frame type: " + type); + } + } + + super.visitFrame(Opcodes.F_NEW, lastFrameLocals.size(), lastFrameLocals.toArray(), lastFrameStack.size(), lastFrameStack.toArray()); + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java index 10bc436a..0f53d263 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java @@ -48,9 +48,9 @@ public final class BuiltinPlugin implements EnigmaPlugin { private void registerEnumNamingService(EnigmaPluginContext ctx) { final Map, String> names = new HashMap<>(); - final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); + JarIndexerService indexerService = JarIndexerService.fromVisitorsInParallel(EnumFieldNameFindingVisitor::new, visitors -> visitors.forEach(visitor -> names.putAll(visitor.mappings))); - ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> JarIndexerService.fromVisitor(visitor)); + ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> indexerService); ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); } @@ -64,13 +64,12 @@ public final class BuiltinPlugin implements EnigmaPlugin { private static final class EnumFieldNameFindingVisitor extends ClassVisitor { private ClassEntry clazz; private String className; - private final Map, String> mappings; + private final Map, String> mappings = new HashMap<>(); private final Set> enumFields = new HashSet<>(); private final List classInits = new ArrayList<>(); - EnumFieldNameFindingVisitor(Map, String> mappings) { + EnumFieldNameFindingVisitor() { super(Enigma.ASM_VERSION); - this.mappings = mappings; } @Override @@ -116,6 +115,10 @@ public final class BuiltinPlugin implements EnigmaPlugin { } private void collectResults() throws Exception { + if (enumFields.isEmpty()) { + return; + } + String owner = className; Analyzer analyzer = new Analyzer<>(new SourceInterpreter()); diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/IndexClassWriter.java b/enigma/src/main/java/cuchaz/enigma/analysis/IndexClassWriter.java new file mode 100644 index 00000000..517efea8 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/IndexClassWriter.java @@ -0,0 +1,133 @@ +package cuchaz.enigma.analysis; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; + +import cuchaz.enigma.analysis.index.EntryIndex; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +public class IndexClassWriter extends ClassWriter { + private final EntryIndex entryIndex; + + public IndexClassWriter(EntryIndex entryIndex, int flags) { + super(flags); + this.entryIndex = entryIndex; + } + + @Override + protected String getCommonSuperClass(String type1, String type2) { + ClassInfo info1 = getClassInfo(type1); + ClassInfo info2 = getClassInfo(type2); + + if (info1 == null || info2 == null) { + return "java/lang/Object"; + } + + if (isAssignable(info1, info2)) { + return type1; + } else if (isAssignable(info2, info1)) { + return type2; + } else if (info1.isInterface() || info2.isInterface()) { + return "java/lang/Object"; + } else { + do { + info1 = info1.getSuperClass(); + } while (info1 != null && !isAssignable(info1, info2)); + + return info1 == null ? "java/lang/Object" : info1.getName(); + } + } + + private boolean isAssignable(ClassInfo left, ClassInfo right) { + String leftName = left.getName(); + + while (right != null) { + if (right.getName().equals(leftName)) { + return true; + } + + right = right.getSuperClass(); + } + + return false; + } + + @Nullable + private ClassInfo getClassInfo(String internalName) { + ClassDefEntry defEntry = entryIndex.getDefinition(new ClassEntry(internalName)); + + if (defEntry != null) { + return new ClassDefEntryInfo(defEntry); + } + + Class clazz; + + try { + clazz = Class.forName(internalName.replace('/', '.'), false, getClassLoader()); + } catch (ClassNotFoundException e) { + return null; + } + + return new ReflectionClassInfo(clazz); + } + + private interface ClassInfo { + String getName(); + + @Nullable + ClassInfo getSuperClass(); + + boolean isInterface(); + } + + private class ClassDefEntryInfo implements ClassInfo { + private final ClassDefEntry entry; + + private ClassDefEntryInfo(ClassDefEntry entry) { + this.entry = entry; + } + + @Override + public String getName() { + return entry.getFullName(); + } + + @Override + @Nullable + public ClassInfo getSuperClass() { + ClassEntry superClass = entry.getSuperClass(); + + if (superClass == null) { + return null; + } + + return getClassInfo(superClass.getFullName()); + } + + @Override + public boolean isInterface() { + return entry.getAccess().isInterface(); + } + } + + private record ReflectionClassInfo(Class clazz) implements ClassInfo { + @Override + public String getName() { + return Type.getInternalName(clazz); + } + + @Override + @Nullable + public ClassInfo getSuperClass() { + Class superClass = clazz.isInterface() ? Object.class : clazz.getSuperclass(); + return superClass == null ? null : new ReflectionClassInfo(superClass); + } + + @Override + public boolean isInterface() { + return clazz.isInterface(); + } + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java b/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java deleted file mode 100644 index 44a768ea..00000000 --- a/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java +++ /dev/null @@ -1,160 +0,0 @@ -package cuchaz.enigma.analysis; - -import java.util.Set; - -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.analysis.BasicValue; -import org.objectweb.asm.tree.analysis.SimpleVerifier; - -import cuchaz.enigma.Enigma; -import cuchaz.enigma.analysis.index.EntryIndex; -import cuchaz.enigma.analysis.index.InheritanceIndex; -import cuchaz.enigma.translation.representation.AccessFlags; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -public class IndexSimpleVerifier extends SimpleVerifier { - private static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;"); - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; - - public IndexSimpleVerifier(EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { - super(Enigma.ASM_VERSION, null, null, null, false); - this.entryIndex = entryIndex; - this.inheritanceIndex = inheritanceIndex; - } - - @Override - protected boolean isSubTypeOf(BasicValue value, BasicValue expected) { - Type expectedType = expected.getType(); - Type type = value.getType(); - switch (expectedType.getSort()) { - case Type.INT: - case Type.FLOAT: - case Type.LONG: - case Type.DOUBLE: - return type.equals(expectedType); - case Type.ARRAY: - case Type.OBJECT: - if (type.equals(NULL_TYPE)) { - return true; - } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { - if (isAssignableFrom(expectedType, type)) { - return true; - } else if (isInterface(expectedType)) { - return isAssignableFrom(OBJECT_TYPE, type); - } else { - return false; - } - } else { - return false; - } - default: - throw new AssertionError(); - } - } - - @Override - protected boolean isInterface(Type type) { - AccessFlags classAccess = entryIndex.getClassAccess(new ClassEntry(type.getInternalName())); - - if (classAccess != null) { - return classAccess.isInterface(); - } - - Class clazz = getClass(type); - - if (clazz != null) { - return clazz.isInterface(); - } - - return false; - } - - @Override - protected Type getSuperClass(Type type) { - ClassDefEntry definition = entryIndex.getDefinition(new ClassEntry(type.getInternalName())); - - if (definition != null) { - return Type.getType('L' + definition.getSuperClass().getFullName() + ';'); - } - - Class clazz = getClass(type); - - if (clazz != null) { - return Type.getType(clazz.getSuperclass()); - } - - return OBJECT_TYPE; - } - - @Override - protected boolean isAssignableFrom(Type type1, Type type2) { - if (type1.equals(type2)) { - return true; - } - - if (type2.equals(NULL_TYPE)) { - return true; - } - - if (type1.getSort() == Type.ARRAY) { - return type2.getSort() == Type.ARRAY && isAssignableFrom(Type.getType(type1.getDescriptor().substring(1)), Type.getType(type2.getDescriptor().substring(1))); - } - - if (type2.getSort() == Type.ARRAY) { - return type1.equals(OBJECT_TYPE); - } - - if (type1.getSort() == Type.OBJECT && type2.getSort() == Type.OBJECT) { - if (type1.equals(OBJECT_TYPE)) { - return true; - } - - ClassEntry class1 = new ClassEntry(type1.getInternalName()); - ClassEntry class2 = new ClassEntry(type2.getInternalName()); - - if (entryIndex.hasClass(class1) && entryIndex.hasClass(class2)) { - return inheritanceIndex.getAncestors(class2).contains(class1); - } - - Class class1Class = getClass(Type.getType('L' + class1.getFullName() + ';')); - Class class2Class = getClass(Type.getType('L' + class2.getFullName() + ';')); - - if (class1Class == null) { - return true; // missing classes to find out - } - - if (class2Class != null) { - return class1Class.isAssignableFrom(class2Class); - } - - if (entryIndex.hasClass(class2)) { - Set ancestors = inheritanceIndex.getAncestors(class2); - - for (ClassEntry ancestorEntry : ancestors) { - Class ancestor = getClass(Type.getType('L' + ancestorEntry.getFullName() + ';')); - - if (ancestor == null || class1Class.isAssignableFrom(ancestor)) { - return true; // assignable, or missing classes to find out - } - } - - return false; - } - - return true; // missing classes to find out - } - - return false; - } - - @Override - protected final Class getClass(Type type) { - try { - return Class.forName(type.getSort() == Type.ARRAY ? type.getDescriptor().replace('/', '.') : type.getClassName(), false, null); - } catch (ClassNotFoundException e) { - return null; - } - } -} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java b/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java deleted file mode 100644 index 2ca1dfd3..00000000 --- a/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java +++ /dev/null @@ -1,106 +0,0 @@ -package cuchaz.enigma.analysis; - -import java.util.List; -import java.util.Objects; - -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.analysis.AnalyzerException; -import org.objectweb.asm.tree.analysis.Interpreter; -import org.objectweb.asm.tree.analysis.Value; - -import cuchaz.enigma.Enigma; - -public class InterpreterPair extends Interpreter> { - private final Interpreter left; - private final Interpreter right; - - public InterpreterPair(Interpreter left, Interpreter right) { - super(Enigma.ASM_VERSION); - this.left = left; - this.right = right; - } - - @Override - public PairValue newValue(Type type) { - return pair(left.newValue(type), right.newValue(type)); - } - - @Override - public PairValue newOperation(AbstractInsnNode insn) throws AnalyzerException { - return pair(left.newOperation(insn), right.newOperation(insn)); - } - - @Override - public PairValue copyOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { - return pair(left.copyOperation(insn, value.left), right.copyOperation(insn, value.right)); - } - - @Override - public PairValue unaryOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { - return pair(left.unaryOperation(insn, value.left), right.unaryOperation(insn, value.right)); - } - - @Override - public PairValue binaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2) throws AnalyzerException { - return pair(left.binaryOperation(insn, value1.left, value2.left), right.binaryOperation(insn, value1.right, value2.right)); - } - - @Override - public PairValue ternaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2, PairValue value3) throws AnalyzerException { - return pair(left.ternaryOperation(insn, value1.left, value2.left, value3.left), right.ternaryOperation(insn, value1.right, value2.right, value3.right)); - } - - @Override - public PairValue naryOperation(AbstractInsnNode insn, List> values) throws AnalyzerException { - return pair(left.naryOperation(insn, values.stream().map(v -> v.left).toList()), right.naryOperation(insn, values.stream().map(v -> v.right).toList())); - } - - @Override - public void returnOperation(AbstractInsnNode insn, PairValue value, PairValue expected) throws AnalyzerException { - left.returnOperation(insn, value.left, expected.left); - right.returnOperation(insn, value.right, expected.right); - } - - @Override - public PairValue merge(PairValue value1, PairValue value2) { - return pair(left.merge(value1.left, value2.left), right.merge(value1.right, value2.right)); - } - - private PairValue pair(V left, W right) { - if (left == null && right == null) { - return null; - } - - return new PairValue<>(left, right); - } - - public static final class PairValue implements Value { - public final V left; - public final W right; - - public PairValue(V left, W right) { - if (left == null && right == null) { - throw new IllegalArgumentException("should use null rather than pair of nulls"); - } - - this.left = left; - this.right = right; - } - - @Override - public boolean equals(Object o) { - return o instanceof InterpreterPair.PairValue pairValue && Objects.equals(left, pairValue.left) && Objects.equals(right, pairValue.right); - } - - @Override - public int hashCode() { - return left.hashCode() * 31 + right.hashCode(); - } - - @Override - public int getSize() { - return (left == null ? right : left).getSize(); - } - } -} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java b/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java deleted file mode 100644 index 8dc7fe68..00000000 --- a/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java +++ /dev/null @@ -1,19 +0,0 @@ -package cuchaz.enigma.analysis; - -import java.util.function.Consumer; - -import org.objectweb.asm.tree.MethodNode; - -public class MethodNodeWithAction extends MethodNode { - private final Consumer action; - - public MethodNodeWithAction(int api, int access, String name, String descriptor, String signature, String[] exceptions, Consumer action) { - super(api, access, name, descriptor, signature, exceptions); - this.action = action; - } - - @Override - public void visitEnd() { - action.accept(this); - } -} diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java b/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java index b3ba8965..95c2c2a1 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java @@ -42,7 +42,7 @@ public class StructureTreeNode extends DefaultMutableTreeNode { } public void load(EnigmaProject project, StructureTreeOptions options) { - Stream children = project.getJarIndex().getChildrenByClass().get(this.parentEntry).stream(); + Stream> children = project.getJarIndex().getChildrenByClass().get(this.parentEntry).stream(); children = switch (options.obfuscationVisibility()) { case ALL -> children; diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java index 26093c3c..4a98c566 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java @@ -5,11 +5,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.annotation.Nullable; -import com.google.common.collect.Maps; - import cuchaz.enigma.translation.representation.AccessFlags; import cuchaz.enigma.translation.representation.MethodDescriptor; import cuchaz.enigma.translation.representation.TypeDescriptor; @@ -22,8 +22,8 @@ public class BridgeMethodIndex implements JarIndexer { private final InheritanceIndex inheritanceIndex; private final ReferenceIndex referenceIndex; - private final Map bridgeToSpecialized = Maps.newHashMap(); - private final Map specializedToBridge = Maps.newHashMap(); + private final ConcurrentMap bridgeToSpecialized = new ConcurrentHashMap<>(); + private final ConcurrentMap specializedToBridge = new ConcurrentHashMap<>(); public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) { this.entryIndex = entryIndex; @@ -50,17 +50,17 @@ public class BridgeMethodIndex implements JarIndexer { public void processIndex(JarIndex index) { Map copiedAccessToBridge = new HashMap<>(specializedToBridge); - for (Map.Entry entry : copiedAccessToBridge.entrySet()) { + copiedAccessToBridge.entrySet().parallelStream().forEach(entry -> { MethodEntry specializedEntry = entry.getKey(); MethodEntry bridgeEntry = entry.getValue(); if (bridgeEntry.getName().equals(specializedEntry.getName())) { - continue; + return; } MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName()); - specializedToBridge.put(renamedSpecializedEntry, specializedToBridge.get(specializedEntry)); - } + specializedToBridge.put(renamedSpecializedEntry, copiedAccessToBridge.get(specializedEntry)); + }); } private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) { diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java index 0e4cdcfa..9e65fc30 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java @@ -1,8 +1,8 @@ package cuchaz.enigma.analysis.index; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import javax.annotation.Nullable; @@ -17,10 +17,10 @@ import cuchaz.enigma.translation.representation.entry.MethodDefEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; public class EntryIndex implements JarIndexer { - private Map classes = new HashMap<>(); - private Map fields = new HashMap<>(); - private Map methods = new HashMap<>(); - private Map definitions = new HashMap<>(); + private final ConcurrentMap classes = new ConcurrentHashMap<>(); + private final ConcurrentMap fields = new ConcurrentHashMap<>(); + private final ConcurrentMap methods = new ConcurrentHashMap<>(); + private final ConcurrentMap definitions = new ConcurrentHashMap<>(); @Override public void indexClass(ClassDefEntry classEntry) { diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java index cb71275a..ecd460de 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java @@ -4,24 +4,12 @@ import java.util.List; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.FieldInsnNode; -import org.objectweb.asm.tree.InvokeDynamicInsnNode; -import org.objectweb.asm.tree.LdcInsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.TypeInsnNode; -import org.objectweb.asm.tree.analysis.Analyzer; -import org.objectweb.asm.tree.analysis.AnalyzerException; -import org.objectweb.asm.tree.analysis.BasicValue; -import org.objectweb.asm.tree.analysis.SourceInterpreter; -import org.objectweb.asm.tree.analysis.SourceValue; - -import cuchaz.enigma.analysis.IndexSimpleVerifier; -import cuchaz.enigma.analysis.InterpreterPair; -import cuchaz.enigma.analysis.MethodNodeWithAction; + +import cuchaz.enigma.analysis.BetterAnalyzerAdapter; import cuchaz.enigma.analysis.ReferenceTargetType; import cuchaz.enigma.translation.representation.AccessFlags; import cuchaz.enigma.translation.representation.Lambda; @@ -35,16 +23,12 @@ import cuchaz.enigma.translation.representation.entry.ParentedEntry; public class IndexReferenceVisitor extends ClassVisitor { private final JarIndexer indexer; - private final EntryIndex entryIndex; - private final InheritanceIndex inheritanceIndex; private ClassEntry classEntry; private String className; - public IndexReferenceVisitor(JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex, int api) { + public IndexReferenceVisitor(JarIndexer indexer, int api) { super(api); this.indexer = indexer; - this.entryIndex = entryIndex; - this.inheritanceIndex = inheritanceIndex; } @Override @@ -56,153 +40,161 @@ public class IndexReferenceVisitor extends ClassVisitor { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); - return new MethodNodeWithAction(api, access, name, desc, signature, exceptions, methodNode -> { - try { - new Analyzer<>(new MethodInterpreter(entry, indexer, entryIndex, inheritanceIndex)).analyze(className, methodNode); - } catch (AnalyzerException e) { - throw new RuntimeException(e); - } - }); + return new IndexReferenceMethodVisitor(api, className, access, name, desc, entry, indexer); } - private static class MethodInterpreter extends InterpreterPair { + private static class IndexReferenceMethodVisitor extends BetterAnalyzerAdapter { private final MethodDefEntry callerEntry; private final JarIndexer indexer; - MethodInterpreter(MethodDefEntry callerEntry, JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { - super(new IndexSimpleVerifier(entryIndex, inheritanceIndex), new SourceInterpreter()); + IndexReferenceMethodVisitor(int api, String owner, int access, String name, String descriptor, MethodDefEntry callerEntry, JarIndexer indexer) { + super(api, owner, access, name, descriptor, null); this.callerEntry = callerEntry; this.indexer = indexer; } @Override - public PairValue newOperation(AbstractInsnNode insn) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.GETSTATIC) { - FieldInsnNode field = (FieldInsnNode) insn; - indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + switch (opcode) { + case Opcodes.GETSTATIC, Opcodes.PUTSTATIC -> indexer.indexFieldReference(callerEntry, FieldEntry.parse(owner, name, descriptor), ReferenceTargetType.none()); + case Opcodes.GETFIELD -> indexer.indexFieldReference(callerEntry, FieldEntry.parse(owner, name, descriptor), getReferenceTargetType(0)); + case Opcodes.PUTFIELD -> indexer.indexFieldReference(callerEntry, FieldEntry.parse(owner, name, descriptor), getReferenceTargetType(Type.getType(descriptor).getSize())); } - if (insn.getOpcode() == Opcodes.LDC) { - LdcInsnNode ldc = (LdcInsnNode) insn; + super.visitFieldInsn(opcode, owner, name, descriptor); + } - if (ldc.getType() == Type.ARRAY && ldc.cst instanceof Type type) { - String className = type.getClassName().replace(".", "/"); - indexer.indexClassReference(callerEntry, ClassEntry.parse(className), ReferenceTargetType.none()); + @Override + public void visitLdcInsn(Object value) { + if (value instanceof Type type && (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY)) { + if (type.getSort() == Type.ARRAY) { + type = type.getElementType(); } + + indexer.indexClassReference(callerEntry, ClassEntry.parse(type.getInternalName()), ReferenceTargetType.none()); } - return super.newOperation(insn); + super.visitLdcInsn(value); } @Override - public PairValue unaryOperation(AbstractInsnNode insn, PairValue value) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.PUTSTATIC) { - FieldInsnNode field = (FieldInsnNode) insn; - indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); - } - - if (insn.getOpcode() == Opcodes.GETFIELD) { - FieldInsnNode field = (FieldInsnNode) insn; - indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), getReferenceTargetType(value, insn)); - } + public void visitTypeInsn(int opcode, String type) { + if (opcode == Opcodes.INSTANCEOF || opcode == Opcodes.CHECKCAST) { + Type classType = Type.getObjectType(type); - if (insn.getOpcode() == Opcodes.INSTANCEOF) { - TypeInsnNode type = (TypeInsnNode) insn; - // Note: type.desc is actually the name - indexer.indexClassReference(callerEntry, ClassEntry.parse(type.desc), ReferenceTargetType.none()); - } + if (classType.getSort() == Type.ARRAY) { + classType = classType.getElementType(); + } - if (insn.getOpcode() == Opcodes.CHECKCAST) { - TypeInsnNode type = (TypeInsnNode) insn; - indexer.indexClassReference(callerEntry, ClassEntry.parse(type.desc), ReferenceTargetType.none()); + indexer.indexClassReference(callerEntry, ClassEntry.parse(classType.getInternalName()), ReferenceTargetType.none()); } - return super.unaryOperation(insn, value); + super.visitTypeInsn(opcode, type); } @Override - public PairValue binaryOperation(AbstractInsnNode insn, PairValue value1, PairValue value2) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.PUTFIELD) { - FieldInsnNode field = (FieldInsnNode) insn; - FieldEntry fieldEntry = FieldEntry.parse(field.owner, field.name, field.desc); - indexer.indexFieldReference(callerEntry, fieldEntry, ReferenceTargetType.none()); + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + ReferenceTargetType targetType; + + if (opcode == Opcodes.INVOKESTATIC) { + targetType = ReferenceTargetType.none(); + } else { + int argSize = (Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1; + targetType = getReferenceTargetType(argSize); } - return super.binaryOperation(insn, value1, value2); + indexer.indexMethodReference(callerEntry, MethodEntry.parse(owner, name, descriptor), targetType); + + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); } @Override - public PairValue naryOperation(AbstractInsnNode insn, List> values) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.INVOKEINTERFACE || insn.getOpcode() == Opcodes.INVOKESPECIAL || insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { - MethodInsnNode methodInsn = (MethodInsnNode) insn; - indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), getReferenceTargetType(values.get(0), insn)); - } - - if (insn.getOpcode() == Opcodes.INVOKESTATIC) { - MethodInsnNode methodInsn = (MethodInsnNode) insn; - indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), ReferenceTargetType.none()); - } - - if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC) { - InvokeDynamicInsnNode invokeDynamicInsn = (InvokeDynamicInsnNode) insn; - List args = values.stream().map(v -> v.right.insns.stream().findFirst().orElseThrow(AssertionError::new)).toList(); - - if ("java/lang/invoke/LambdaMetafactory".equals(invokeDynamicInsn.bsm.getOwner()) && "metafactory".equals(invokeDynamicInsn.bsm.getName())) { - Type samMethodType = (Type) invokeDynamicInsn.bsmArgs[0]; - Handle implMethod = (Handle) invokeDynamicInsn.bsmArgs[1]; - Type instantiatedMethodType = (Type) invokeDynamicInsn.bsmArgs[2]; - - ReferenceTargetType targetType; - - if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) { - if (instantiatedMethodType.getArgumentTypes().length < Type.getArgumentTypes(implMethod.getDesc()).length) { - targetType = getReferenceTargetType(values.get(0), insn); + public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { + if ("java/lang/invoke/LambdaMetafactory".equals(bootstrapMethodHandle.getOwner()) && ("metafactory".equals(bootstrapMethodHandle.getName()) || "altMetafactory".equals(bootstrapMethodHandle.getName()))) { + Type samMethodType = (Type) bootstrapMethodArguments[0]; + Handle implMethod = (Handle) bootstrapMethodArguments[1]; + Type instantiatedMethodType = (Type) bootstrapMethodArguments[2]; + + ReferenceTargetType targetType; + + if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) { + if (instantiatedMethodType.getArgumentCount() < Type.getArgumentCount(implMethod.getDesc())) { + if (descriptor.startsWith("(L")) { // is the first parameter of the indy an object type? + int argSize = (Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1; + targetType = getReferenceTargetType(argSize - 1); } else { - targetType = ReferenceTargetType.none(); // no "this" argument + targetType = ReferenceTargetType.none(); } } else { - targetType = ReferenceTargetType.none(); + targetType = ReferenceTargetType.none(); // no "this" argument } - - indexer.indexLambda(callerEntry, new Lambda(invokeDynamicInsn.name, new MethodDescriptor(invokeDynamicInsn.desc), new MethodDescriptor(samMethodType.getDescriptor()), getHandleEntry(implMethod), new MethodDescriptor(instantiatedMethodType.getDescriptor())), targetType); + } else { + targetType = ReferenceTargetType.none(); } + + indexer.indexLambda(callerEntry, new Lambda(name, new MethodDescriptor(descriptor), new MethodDescriptor(samMethodType.getDescriptor()), getHandleEntry(implMethod), new MethodDescriptor(instantiatedMethodType.getDescriptor())), targetType); } - return super.naryOperation(insn, values); + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); } - private ReferenceTargetType getReferenceTargetType(PairValue target, AbstractInsnNode insn) throws AnalyzerException { - if (target.left == BasicValue.UNINITIALIZED_VALUE) { + private ReferenceTargetType getReferenceTargetType(int stackDepth) { + if (stackDepth >= stack.size()) { + throw new IllegalStateException("Stack depth " + stackDepth + " is higher than the stack: " + stackValuesToString(stack) + " in method " + callerEntry); + } + + Object stackValue = stack.get(stack.size() - 1 - stackDepth); + + if (stackValue.equals(Opcodes.UNINITIALIZED_THIS) || stackValue instanceof Label) { return ReferenceTargetType.uninitialized(); } - if (target.left.getType().getSort() == Type.OBJECT) { - return ReferenceTargetType.classType(new ClassEntry(target.left.getType().getInternalName())); + if (!(stackValue instanceof String type)) { + throw new IllegalStateException("Illegal stack value in method " + callerEntry + ": " + stackValuesToString(List.of(stackValue))); } - if (target.left.getType().getSort() == Type.ARRAY) { + if (type.startsWith("[")) { + // array type return ReferenceTargetType.classType(new ClassEntry("java/lang/Object")); + } else { + return ReferenceTargetType.classType(new ClassEntry(type)); } - - throw new AnalyzerException(insn, "called method on or accessed field of non-object type"); } - private static ParentedEntry getHandleEntry(Handle handle) { - switch (handle.getTag()) { - case Opcodes.H_GETFIELD: - case Opcodes.H_GETSTATIC: - case Opcodes.H_PUTFIELD: - case Opcodes.H_PUTSTATIC: - return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); - case Opcodes.H_INVOKEINTERFACE: - case Opcodes.H_INVOKESPECIAL: - case Opcodes.H_INVOKESTATIC: - case Opcodes.H_INVOKEVIRTUAL: - case Opcodes.H_NEWINVOKESPECIAL: - return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + private static String stackValuesToString(List stack) { + StringBuilder result = new StringBuilder("["); + boolean first = true; + + for (Object stackValue : stack) { + if (first) { + first = false; + } else { + result.append(", "); + } + + if (stackValue instanceof String str) { + result.append(str); + } else if (stackValue instanceof Integer i) { + result.append("TIFDJNU".charAt(i)); + } else if (stackValue instanceof Label) { + result.append('U'); + } else { + throw new AssertionError("Illegal stack value type: " + stackValue.getClass().getName()); + } } - throw new RuntimeException("Invalid handle tag " + handle.getTag()); + return result.append(']').toString(); + } + + private static ParentedEntry getHandleEntry(Handle handle) { + return switch (handle.getTag()) { + case Opcodes.H_GETFIELD, Opcodes.H_GETSTATIC, Opcodes.H_PUTFIELD, Opcodes.H_PUTSTATIC -> + FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + case Opcodes.H_INVOKEINTERFACE, Opcodes.H_INVOKESPECIAL, Opcodes.H_INVOKESTATIC, + Opcodes.H_INVOKEVIRTUAL, Opcodes.H_NEWINVOKESPECIAL -> + MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + default -> throw new RuntimeException("Invalid handle tag " + handle.getTag()); + }; } } } diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java index 1c60db96..9841aa69 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java @@ -11,13 +11,16 @@ package cuchaz.enigma.analysis.index; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; +import java.util.List; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import cuchaz.enigma.translation.representation.entry.ClassDefEntry; @@ -26,8 +29,8 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry; public class InheritanceIndex implements JarIndexer { private final EntryIndex entryIndex; - private Multimap classParents = HashMultimap.create(); - private Multimap classChildren = HashMultimap.create(); + private final ConcurrentMap> classParents = new ConcurrentHashMap<>(); + private final ConcurrentMap> classChildren = new ConcurrentHashMap<>(); public InheritanceIndex(EntryIndex entryIndex) { this.entryIndex = entryIndex; @@ -51,16 +54,18 @@ public class InheritanceIndex implements JarIndexer { } private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) { - classParents.put(childEntry, parentEntry); - classChildren.put(parentEntry, childEntry); + // No need to add to classParents in a synchronized way, as we'll be the only ones adding to the corresponding childEntry + classParents.computeIfAbsent(childEntry, k -> new ArrayList<>()).add(parentEntry); + + JarIndex.synchronizedAdd(classChildren, parentEntry, childEntry); } public Collection getParents(ClassEntry classEntry) { - return classParents.get(classEntry); + return classParents.getOrDefault(classEntry, Collections.emptyList()); } public Collection getChildren(ClassEntry classEntry) { - return classChildren.get(classEntry); + return classChildren.getOrDefault(classEntry, Collections.emptyList()); } public Collection getDescendants(ClassEntry classEntry) { diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java index 967983a8..cfa177e9 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java @@ -11,19 +11,20 @@ package cuchaz.enigma.analysis.index; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; - -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Multimap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import cuchaz.enigma.Enigma; import cuchaz.enigma.ProgressListener; import cuchaz.enigma.analysis.ReferenceTargetType; +import cuchaz.enigma.classprovider.AddFramesIfNecessaryClassProvider; +import cuchaz.enigma.classprovider.CachingClassProvider; import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.IndexEntryResolver; @@ -48,8 +49,7 @@ public class JarIndex implements JarIndexer { private final Collection indexers; - private final Multimap methodImplementations = HashMultimap.create(); - private final ListMultimap childrenByClass; + private final ConcurrentMap>> childrenByClass; public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { this.entryIndex = entryIndex; @@ -59,7 +59,7 @@ public class JarIndex implements JarIndexer { this.packageVisibilityIndex = packageVisibilityIndex; this.indexers = List.of(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); this.entryResolver = new IndexEntryResolver(this); - this.childrenByClass = ArrayListMultimap.create(); + this.childrenByClass = new ConcurrentHashMap<>(); } public static JarIndex empty() { @@ -71,31 +71,35 @@ public class JarIndex implements JarIndexer { return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); } - public void indexJar(Set classNames, ClassProvider classProvider, ProgressListener progress) { + public ClassProvider indexJar(Set classNames, ClassProvider classProvider, ProgressListener progress) { indexedClasses.addAll(classNames); progress.init(4, I18n.translate("progress.jar.indexing")); progress.step(1, I18n.translate("progress.jar.indexing.entries")); - for (String className : classNames) { + classNames.parallelStream().forEach(className -> { classProvider.get(className).accept(new IndexClassVisitor(this, Enigma.ASM_VERSION)); - } + }); + + ClassProvider classProviderWithFrames = new CachingClassProvider(new AddFramesIfNecessaryClassProvider(classProvider, entryIndex)); progress.step(2, I18n.translate("progress.jar.indexing.references")); - for (String className : classNames) { + classNames.parallelStream().forEach(className -> { try { - classProvider.get(className).accept(new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION)); + classProviderWithFrames.get(className).accept(new IndexReferenceVisitor(this, Enigma.ASM_VERSION)); } catch (Exception e) { throw new RuntimeException("Exception while indexing class: " + className, e); } - } + }); progress.step(3, I18n.translate("progress.jar.indexing.methods")); bridgeMethodIndex.findBridgeMethods(); progress.step(4, I18n.translate("progress.jar.indexing.process")); processIndex(this); + + return classProviderWithFrames; } @Override @@ -118,7 +122,7 @@ public class JarIndex implements JarIndexer { indexers.forEach(indexer -> indexer.indexClass(classEntry)); if (classEntry.isInnerClass() && !classEntry.getAccess().isSynthetic()) { - childrenByClass.put(classEntry.getParent(), classEntry); + synchronizedAdd(childrenByClass, classEntry.getParent(), classEntry); } } @@ -131,7 +135,7 @@ public class JarIndex implements JarIndexer { indexers.forEach(indexer -> indexer.indexField(fieldEntry)); if (!fieldEntry.getAccess().isSynthetic()) { - childrenByClass.put(fieldEntry.getParent(), fieldEntry); + synchronizedAdd(childrenByClass, fieldEntry.getParent(), fieldEntry); } } @@ -144,11 +148,7 @@ public class JarIndex implements JarIndexer { indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); if (!methodEntry.getAccess().isSynthetic() && !methodEntry.getName().equals("")) { - childrenByClass.put(methodEntry.getParent(), methodEntry); - } - - if (!methodEntry.isConstructor()) { - methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); + synchronizedAdd(childrenByClass, methodEntry.getParent(), methodEntry); } } @@ -212,11 +212,18 @@ public class JarIndex implements JarIndexer { return entryResolver; } - public ListMultimap getChildrenByClass() { + public Map>> getChildrenByClass() { return this.childrenByClass; } public boolean isIndexed(String internalName) { return indexedClasses.contains(internalName); } + + static void synchronizedAdd(ConcurrentMap> map, K key, V value) { + List list = map.computeIfAbsent(key, k -> new ArrayList<>()); + synchronized (list) { + list.add(value); + } + } } diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java index b400a66c..a2ba0298 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java @@ -6,8 +6,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; -import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -46,19 +47,25 @@ public class PackageVisibilityIndex implements JarIndexer { return true; } - private final HashMultimap connections = HashMultimap.create(); + private final ConcurrentMap> connections = new ConcurrentHashMap<>(); private final List> partitions = Lists.newArrayList(); private final Map> classPartitions = Maps.newHashMap(); private void addConnection(ClassEntry classA, ClassEntry classB) { if (classA != classB) { - connections.put(classA, classB); - connections.put(classB, classA); + JarIndex.synchronizedAdd(connections, classA, classB); + JarIndex.synchronizedAdd(connections, classB, classA); } } private void buildPartition(Set unassignedClasses, Set partition, ClassEntry member) { - for (ClassEntry connected : connections.get(member)) { + List memberConnections = connections.get(member); + + if (memberConnections == null) { + return; + } + + for (ClassEntry connected : memberConnections) { if (unassignedClasses.remove(connected)) { partition.add(connected); buildPartition(unassignedClasses, partition, connected); @@ -67,7 +74,7 @@ public class PackageVisibilityIndex implements JarIndexer { } private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) { - for (FieldEntry entry : entryIndex.getFields()) { + entryIndex.getFields().parallelStream().forEach(entry -> { AccessFlags entryAcc = entryIndex.getFieldAccess(entry); if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { @@ -77,9 +84,9 @@ public class PackageVisibilityIndex implements JarIndexer { } } } - } + }); - for (MethodEntry entry : entryIndex.getMethods()) { + entryIndex.getMethods().parallelStream().forEach(entry -> { AccessFlags entryAcc = entryIndex.getMethodAccess(entry); if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { @@ -89,9 +96,9 @@ public class PackageVisibilityIndex implements JarIndexer { } } } - } + }); - for (ClassEntry entry : entryIndex.getClasses()) { + entryIndex.getClasses().parallelStream().forEach(entry -> { AccessFlags entryAcc = entryIndex.getClassAccess(entry); if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { @@ -121,7 +128,7 @@ public class PackageVisibilityIndex implements JarIndexer { if (outerClass != null) { addConnection(entry, outerClass); } - } + }); } private void addPartitions(EntryIndex entryIndex) { diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java index c55d36ad..1a406cb8 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java @@ -1,10 +1,10 @@ package cuchaz.enigma.analysis.index; import java.util.Collection; -import java.util.Map; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.ReferenceTargetType; @@ -20,13 +20,13 @@ import cuchaz.enigma.translation.representation.entry.MethodDefEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; public class ReferenceIndex implements JarIndexer { - private Multimap methodReferences = HashMultimap.create(); + private ConcurrentMap> methodReferences = new ConcurrentHashMap<>(); - private Multimap> referencesToMethods = HashMultimap.create(); - private Multimap> referencesToClasses = HashMultimap.create(); - private Multimap> referencesToFields = HashMultimap.create(); - private Multimap> fieldTypeReferences = HashMultimap.create(); - private Multimap> methodTypeReferences = HashMultimap.create(); + private ConcurrentMap>> referencesToMethods = new ConcurrentHashMap<>(); + private ConcurrentMap>> referencesToClasses = new ConcurrentHashMap<>(); + private ConcurrentMap>> referencesToFields = new ConcurrentHashMap<>(); + private ConcurrentMap>> fieldTypeReferences = new ConcurrentHashMap<>(); + private ConcurrentMap>> methodTypeReferences = new ConcurrentHashMap<>(); @Override public void indexMethod(MethodDefEntry methodEntry) { @@ -44,7 +44,7 @@ public class ReferenceIndex implements JarIndexer { private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { if (typeDescriptor.isType()) { ClassEntry referencedClass = typeDescriptor.getTypeEntry(); - methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); + JarIndex.synchronizedAdd(methodTypeReferences, referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); } else if (typeDescriptor.isArray()) { indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); } @@ -58,7 +58,7 @@ public class ReferenceIndex implements JarIndexer { private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { if (typeDescriptor.isType()) { ClassEntry referencedClass = typeDescriptor.getTypeEntry(); - fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); + JarIndex.synchronizedAdd(fieldTypeReferences, referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); } else if (typeDescriptor.isArray()) { indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); } @@ -66,23 +66,23 @@ public class ReferenceIndex implements JarIndexer { @Override public void indexClassReference(MethodDefEntry callerEntry, ClassEntry referencedEntry, ReferenceTargetType targetType) { - referencesToClasses.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + JarIndex.synchronizedAdd(referencesToClasses, referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); } @Override public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { - referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); - methodReferences.put(callerEntry, referencedEntry); + JarIndex.synchronizedAdd(referencesToMethods, referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + JarIndex.synchronizedAdd(methodReferences, callerEntry, referencedEntry); if (referencedEntry.isConstructor()) { ClassEntry referencedClass = referencedEntry.getParent(); - referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); + JarIndex.synchronizedAdd(referencesToClasses, referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); } } @Override public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { - referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); + JarIndex.synchronizedAdd(referencesToFields, referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); } @Override @@ -108,24 +108,26 @@ public class ReferenceIndex implements JarIndexer { methodTypeReferences = remapReferencesTo(index, methodTypeReferences); } - private , V extends Entry> Multimap remapReferences(JarIndex index, Multimap multimap) { - final int keySetSize = multimap.keySet().size(); - Multimap resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize); + private , V extends Entry> ConcurrentMap> remapReferences(JarIndex index, ConcurrentMap> multimap) { + ConcurrentMap> resolved = new ConcurrentHashMap<>(); - for (Map.Entry entry : multimap.entries()) { - resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); - } + multimap.entrySet().parallelStream().forEach(entry -> { + for (V value : entry.getValue()) { + JarIndex.synchronizedAdd(resolved, remap(index, entry.getKey()), remap(index, value)); + } + }); return resolved; } - private , C extends Entry> Multimap> remapReferencesTo(JarIndex index, Multimap> multimap) { - final int keySetSize = multimap.keySet().size(); - Multimap> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); + private , C extends Entry> ConcurrentMap>> remapReferencesTo(JarIndex index, ConcurrentMap>> multimap) { + ConcurrentMap>> resolved = new ConcurrentHashMap<>(); - for (Map.Entry> entry : multimap.entries()) { - resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); - } + multimap.entrySet().parallelStream().forEach(entry -> { + for (EntryReference value : entry.getValue()) { + JarIndex.synchronizedAdd(resolved, remap(index, entry.getKey()), remap(index, value)); + } + }); return resolved; } @@ -139,26 +141,26 @@ public class ReferenceIndex implements JarIndexer { } public Collection getMethodsReferencedBy(MethodEntry entry) { - return methodReferences.get(entry); + return methodReferences.getOrDefault(entry, Collections.emptyList()); } public Collection> getReferencesToField(FieldEntry entry) { - return referencesToFields.get(entry); + return referencesToFields.getOrDefault(entry, Collections.emptyList()); } public Collection> getReferencesToClass(ClassEntry entry) { - return referencesToClasses.get(entry); + return referencesToClasses.getOrDefault(entry, Collections.emptyList()); } public Collection> getReferencesToMethod(MethodEntry entry) { - return referencesToMethods.get(entry); + return referencesToMethods.getOrDefault(entry, Collections.emptyList()); } public Collection> getFieldTypeReferencesToClass(ClassEntry entry) { - return fieldTypeReferences.get(entry); + return fieldTypeReferences.getOrDefault(entry, Collections.emptyList()); } public Collection> getMethodTypeReferencesToClass(ClassEntry entry) { - return methodTypeReferences.get(entry); + return methodTypeReferences.getOrDefault(entry, Collections.emptyList()); } } diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java index 3ed6d338..91c79c6d 100644 --- a/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java +++ b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java @@ -1,6 +1,10 @@ package cuchaz.enigma.api.service; +import java.util.Collection; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; +import java.util.function.Supplier; import org.objectweb.asm.ClassVisitor; @@ -19,4 +23,21 @@ public interface JarIndexerService extends EnigmaService { } }; } + + /** + * Creates multiple thread-local {@code ClassVisitor}s, runs each on a subset of classes on their own thread, and + * combines them at the end. + */ + static JarIndexerService fromVisitorsInParallel(Supplier visitorCreator, Consumer> combiner) { + return (scope, classProvider, jarIndex) -> { + CopyOnWriteArrayList allVisitors = new CopyOnWriteArrayList<>(); + ThreadLocal visitors = ThreadLocal.withInitial(() -> { + V visitor = visitorCreator.get(); + allVisitors.add(visitor); + return visitor; + }); + scope.parallelStream().forEach(className -> classProvider.get(className).accept(visitors.get())); + combiner.accept(allVisitors); + }; + } } diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/AddFramesIfNecessaryClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/AddFramesIfNecessaryClassProvider.java new file mode 100644 index 00000000..532631f9 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/AddFramesIfNecessaryClassProvider.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.classprovider; + +import java.util.Collection; + +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; + +import cuchaz.enigma.analysis.IndexClassWriter; +import cuchaz.enigma.analysis.index.EntryIndex; + +public class AddFramesIfNecessaryClassProvider implements ClassProvider { + private final ClassProvider delegate; + private final EntryIndex entryIndex; + + public AddFramesIfNecessaryClassProvider(ClassProvider delegate, EntryIndex entryIndex) { + this.delegate = delegate; + this.entryIndex = entryIndex; + } + + @Override + public Collection getClassNames() { + return delegate.getClassNames(); + } + + @Override + @Nullable + public ClassNode get(String name) { + ClassNode clazz = delegate.get(name); + + if (clazz == null) { + return null; + } + + if (clazz.version >= Opcodes.V1_7) { + // already has frames + return clazz; + } + + IndexClassWriter cw = new IndexClassWriter(entryIndex, ClassWriter.COMPUTE_FRAMES); + clazz.accept(cw); + ClassReader cr = new ClassReader(cw.toByteArray()); + ClassNode node = new ClassNode(); + cr.accept(node, 0); + return node; + } +} diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java index b7696d2d..d4e79e18 100644 --- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java +++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java @@ -9,15 +9,15 @@ import java.util.stream.StreamSupport; import javax.annotation.Nullable; -import org.jetbrains.annotations.ApiStatus; import net.fabricmc.mappingio.MappedElementKind; -import net.fabricmc.mappingio.tree.MemoryMappingTree; -import net.fabricmc.mappingio.tree.VisitableMappingTree; import net.fabricmc.mappingio.tree.MappingTree.ClassMapping; import net.fabricmc.mappingio.tree.MappingTree.FieldMapping; import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping; import net.fabricmc.mappingio.tree.MappingTree.MethodMapping; import net.fabricmc.mappingio.tree.MappingTree.MethodVarMapping; +import net.fabricmc.mappingio.tree.MemoryMappingTree; +import net.fabricmc.mappingio.tree.VisitableMappingTree; +import org.jetbrains.annotations.ApiStatus; import cuchaz.enigma.ProgressListener; import cuchaz.enigma.analysis.index.JarIndex; diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java index 3f6f1511..8bd58596 100644 --- a/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java +++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java @@ -11,11 +11,11 @@ package cuchaz.enigma; +import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; import static cuchaz.enigma.TestEntryFactory.newClass; import static cuchaz.enigma.TestEntryFactory.newField; -import static cuchaz.enigma.TestEntryFactory.newMethod; import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; +import static cuchaz.enigma.TestEntryFactory.newMethod; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; -- cgit v1.2.3