summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Joseph Burton2025-08-06 14:32:45 +0100
committerGravatar GitHub2025-08-06 14:32:45 +0100
commitcd38b03621928e64ddb449e4e63a511c0b371f94 (patch)
tree5b3b145b63c35af5eee459902d7af859c476e6ec
parentBump version (diff)
downloadenigma-cd38b03621928e64ddb449e4e63a511c0b371f94.tar.gz
enigma-cd38b03621928e64ddb449e4e63a511c0b371f94.tar.xz
enigma-cd38b03621928e64ddb449e4e63a511c0b371f94.zip
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
-rw-r--r--build.gradle11
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java2
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/util/GuiUtil.java4
-rw-r--r--enigma/src/main/java/cuchaz/enigma/Enigma.java4
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/BetterAnalyzerAdapter.java65
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java13
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/IndexClassWriter.java133
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java160
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java106
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java19
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/StructureTreeNode.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java16
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java224
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java21
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java51
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java29
-rw-r--r--enigma/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java72
-rw-r--r--enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java21
-rw-r--r--enigma/src/main/java/cuchaz/enigma/classprovider/AddFramesIfNecessaryClassProvider.java49
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingIoConverter.java6
-rw-r--r--enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java4
22 files changed, 517 insertions, 507 deletions
diff --git a/build.gradle b/build.gradle
index 46794c5b..4453ab93 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,12 +1,14 @@
1plugins { 1plugins {
2 id 'maven-publish' 2 id 'maven-publish'
3 id 'com.github.johnrengelman.shadow' version '8.1.1' apply false 3 id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
4 id 'com.diffplug.spotless' version '7.2.1'
4} 5}
5 6
6subprojects { 7subprojects {
7 apply plugin: 'java' 8 apply plugin: 'java'
8 apply plugin: 'maven-publish' 9 apply plugin: 'maven-publish'
9 apply plugin: 'checkstyle' 10 apply plugin: 'checkstyle'
11 apply plugin: "com.diffplug.spotless"
10 12
11 repositories { 13 repositories {
12 mavenLocal() 14 mavenLocal()
@@ -47,6 +49,15 @@ subprojects {
47 toolVersion = '10.12.4' 49 toolVersion = '10.12.4'
48 } 50 }
49 51
52 spotless {
53 lineEndings = com.diffplug.spotless.LineEnding.UNIX
54
55 java {
56 removeUnusedImports()
57 importOrder('java', 'javax', '', 'cuchaz.enigma')
58 }
59 }
60
50 publishing { 61 publishing {
51 publications { 62 publications {
52 "$project.name"(MavenPublication) { 63 "$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 {
95 }, SwingUtilities::invokeLater).thenAcceptAsync(progress -> { 95 }, SwingUtilities::invokeLater).thenAcceptAsync(progress -> {
96 try (progress) { 96 try (progress) {
97 runnable.run(progress); 97 runnable.run(progress);
98 } catch (Exception e) { 98 } catch (Throwable e) {
99 CrashDialog.show(e); 99 CrashDialog.show(e);
100 throw new RuntimeException(e); 100 throw new RuntimeException(e);
101 } 101 }
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;
5import java.awt.Desktop; 5import java.awt.Desktop;
6import java.awt.Font; 6import java.awt.Font;
7import java.awt.Toolkit; 7import java.awt.Toolkit;
8import java.awt.datatransfer.DataFlavor;
8import java.awt.datatransfer.StringSelection; 9import java.awt.datatransfer.StringSelection;
10import java.awt.datatransfer.UnsupportedFlavorException;
9import java.awt.event.MouseAdapter; 11import java.awt.event.MouseAdapter;
10import java.awt.event.MouseEvent; 12import java.awt.event.MouseEvent;
11import java.awt.event.MouseListener; 13import java.awt.event.MouseListener;
12import java.awt.event.WindowAdapter; 14import java.awt.event.WindowAdapter;
13import java.awt.event.WindowEvent; 15import java.awt.event.WindowEvent;
14import java.awt.event.WindowListener; 16import java.awt.event.WindowListener;
15import java.awt.datatransfer.DataFlavor;
16import java.awt.datatransfer.UnsupportedFlavorException;
17import java.awt.font.TextAttribute; 17import java.awt.font.TextAttribute;
18import java.io.IOException; 18import java.io.IOException;
19import java.net.URI; 19import 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 {
62 Set<String> scope = jarClassProvider.getClassNames(); 62 Set<String> scope = jarClassProvider.getClassNames();
63 63
64 JarIndex index = JarIndex.empty(); 64 JarIndex index = JarIndex.empty();
65 index.indexJar(scope, classProvider, progress); 65 ClassProvider classProviderWithFrames = index.indexJar(scope, classProvider, progress);
66 services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProvider, index)); 66 services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProviderWithFrames, index));
67 67
68 return new EnigmaProject(this, path, classProvider, index, Utils.zipSha1(path)); 68 return new EnigmaProject(this, path, classProvider, index, Utils.zipSha1(path));
69 } 69 }
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 @@
1package cuchaz.enigma.analysis;
2
3import java.util.ArrayList;
4import java.util.Collections;
5import java.util.List;
6
7import org.jetbrains.annotations.Nullable;
8import org.objectweb.asm.ClassReader;
9import org.objectweb.asm.MethodVisitor;
10import org.objectweb.asm.Opcodes;
11import org.objectweb.asm.commons.AnalyzerAdapter;
12
13/**
14 * An {@link AnalyzerAdapter} that works even if the class wasn't read with {@link ClassReader#EXPAND_FRAMES}.
15 */
16public class BetterAnalyzerAdapter extends AnalyzerAdapter {
17 private final List<Object> lastFrameLocals = new ArrayList<>();
18 private final List<Object> lastFrameStack = new ArrayList<>();
19
20 protected BetterAnalyzerAdapter(int api, String owner, int access, String name, String descriptor, @Nullable MethodVisitor methodVisitor) {
21 super(api, owner, access, name, descriptor, methodVisitor);
22
23 for (Object local : this.locals) {
24 if (!local.equals(Opcodes.TOP)) {
25 lastFrameLocals.add(local);
26 }
27 }
28 }
29
30 @Override
31 public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) {
32 switch (type) {
33 case Opcodes.F_NEW -> {
34 super.visitFrame(type, numLocal, local, numStack, stack);
35 return;
36 }
37 case Opcodes.F_SAME -> {
38 lastFrameStack.clear();
39 }
40 case Opcodes.F_SAME1 -> {
41 lastFrameStack.clear();
42 lastFrameStack.add(stack[0]);
43 }
44 case Opcodes.F_APPEND -> {
45 Collections.addAll(lastFrameLocals, local);
46 lastFrameStack.clear();
47 }
48 case Opcodes.F_CHOP -> {
49 lastFrameLocals.subList(lastFrameLocals.size() - numLocal, lastFrameLocals.size()).clear();
50 lastFrameStack.clear();
51 }
52 case Opcodes.F_FULL -> {
53 lastFrameLocals.clear();
54 Collections.addAll(lastFrameLocals, local);
55 lastFrameStack.clear();
56 Collections.addAll(lastFrameStack, stack);
57 }
58 default -> {
59 throw new AssertionError("Illegal frame type: " + type);
60 }
61 }
62
63 super.visitFrame(Opcodes.F_NEW, lastFrameLocals.size(), lastFrameLocals.toArray(), lastFrameStack.size(), lastFrameStack.toArray());
64 }
65}
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 {
48 48
49 private void registerEnumNamingService(EnigmaPluginContext ctx) { 49 private void registerEnumNamingService(EnigmaPluginContext ctx) {
50 final Map<Entry<?>, String> names = new HashMap<>(); 50 final Map<Entry<?>, String> names = new HashMap<>();
51 final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); 51 JarIndexerService indexerService = JarIndexerService.fromVisitorsInParallel(EnumFieldNameFindingVisitor::new, visitors -> visitors.forEach(visitor -> names.putAll(visitor.mappings)));
52 52
53 ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> JarIndexerService.fromVisitor(visitor)); 53 ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> indexerService);
54 ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); 54 ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry)));
55 } 55 }
56 56
@@ -64,13 +64,12 @@ public final class BuiltinPlugin implements EnigmaPlugin {
64 private static final class EnumFieldNameFindingVisitor extends ClassVisitor { 64 private static final class EnumFieldNameFindingVisitor extends ClassVisitor {
65 private ClassEntry clazz; 65 private ClassEntry clazz;
66 private String className; 66 private String className;
67 private final Map<Entry<?>, String> mappings; 67 private final Map<Entry<?>, String> mappings = new HashMap<>();
68 private final Set<Pair<String, String>> enumFields = new HashSet<>(); 68 private final Set<Pair<String, String>> enumFields = new HashSet<>();
69 private final List<MethodNode> classInits = new ArrayList<>(); 69 private final List<MethodNode> classInits = new ArrayList<>();
70 70
71 EnumFieldNameFindingVisitor(Map<Entry<?>, String> mappings) { 71 EnumFieldNameFindingVisitor() {
72 super(Enigma.ASM_VERSION); 72 super(Enigma.ASM_VERSION);
73 this.mappings = mappings;
74 } 73 }
75 74
76 @Override 75 @Override
@@ -116,6 +115,10 @@ public final class BuiltinPlugin implements EnigmaPlugin {
116 } 115 }
117 116
118 private void collectResults() throws Exception { 117 private void collectResults() throws Exception {
118 if (enumFields.isEmpty()) {
119 return;
120 }
121
119 String owner = className; 122 String owner = className;
120 Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter()); 123 Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter());
121 124
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 @@
1package cuchaz.enigma.analysis;
2
3import org.jetbrains.annotations.Nullable;
4import org.objectweb.asm.ClassWriter;
5import org.objectweb.asm.Type;
6
7import cuchaz.enigma.analysis.index.EntryIndex;
8import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
10
11public class IndexClassWriter extends ClassWriter {
12 private final EntryIndex entryIndex;
13
14 public IndexClassWriter(EntryIndex entryIndex, int flags) {
15 super(flags);
16 this.entryIndex = entryIndex;
17 }
18
19 @Override
20 protected String getCommonSuperClass(String type1, String type2) {
21 ClassInfo info1 = getClassInfo(type1);
22 ClassInfo info2 = getClassInfo(type2);
23
24 if (info1 == null || info2 == null) {
25 return "java/lang/Object";
26 }
27
28 if (isAssignable(info1, info2)) {
29 return type1;
30 } else if (isAssignable(info2, info1)) {
31 return type2;
32 } else if (info1.isInterface() || info2.isInterface()) {
33 return "java/lang/Object";
34 } else {
35 do {
36 info1 = info1.getSuperClass();
37 } while (info1 != null && !isAssignable(info1, info2));
38
39 return info1 == null ? "java/lang/Object" : info1.getName();
40 }
41 }
42
43 private boolean isAssignable(ClassInfo left, ClassInfo right) {
44 String leftName = left.getName();
45
46 while (right != null) {
47 if (right.getName().equals(leftName)) {
48 return true;
49 }
50
51 right = right.getSuperClass();
52 }
53
54 return false;
55 }
56
57 @Nullable
58 private ClassInfo getClassInfo(String internalName) {
59 ClassDefEntry defEntry = entryIndex.getDefinition(new ClassEntry(internalName));
60
61 if (defEntry != null) {
62 return new ClassDefEntryInfo(defEntry);
63 }
64
65 Class<?> clazz;
66
67 try {
68 clazz = Class.forName(internalName.replace('/', '.'), false, getClassLoader());
69 } catch (ClassNotFoundException e) {
70 return null;
71 }
72
73 return new ReflectionClassInfo(clazz);
74 }
75
76 private interface ClassInfo {
77 String getName();
78
79 @Nullable
80 ClassInfo getSuperClass();
81
82 boolean isInterface();
83 }
84
85 private class ClassDefEntryInfo implements ClassInfo {
86 private final ClassDefEntry entry;
87
88 private ClassDefEntryInfo(ClassDefEntry entry) {
89 this.entry = entry;
90 }
91
92 @Override
93 public String getName() {
94 return entry.getFullName();
95 }
96
97 @Override
98 @Nullable
99 public ClassInfo getSuperClass() {
100 ClassEntry superClass = entry.getSuperClass();
101
102 if (superClass == null) {
103 return null;
104 }
105
106 return getClassInfo(superClass.getFullName());
107 }
108
109 @Override
110 public boolean isInterface() {
111 return entry.getAccess().isInterface();
112 }
113 }
114
115 private record ReflectionClassInfo(Class<?> clazz) implements ClassInfo {
116 @Override
117 public String getName() {
118 return Type.getInternalName(clazz);
119 }
120
121 @Override
122 @Nullable
123 public ClassInfo getSuperClass() {
124 Class<?> superClass = clazz.isInterface() ? Object.class : clazz.getSuperclass();
125 return superClass == null ? null : new ReflectionClassInfo(superClass);
126 }
127
128 @Override
129 public boolean isInterface() {
130 return clazz.isInterface();
131 }
132 }
133}
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 @@
1package cuchaz.enigma.analysis;
2
3import java.util.Set;
4
5import org.objectweb.asm.Type;
6import org.objectweb.asm.tree.analysis.BasicValue;
7import org.objectweb.asm.tree.analysis.SimpleVerifier;
8
9import cuchaz.enigma.Enigma;
10import cuchaz.enigma.analysis.index.EntryIndex;
11import cuchaz.enigma.analysis.index.InheritanceIndex;
12import cuchaz.enigma.translation.representation.AccessFlags;
13import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15
16public class IndexSimpleVerifier extends SimpleVerifier {
17 private static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;");
18 private final EntryIndex entryIndex;
19 private final InheritanceIndex inheritanceIndex;
20
21 public IndexSimpleVerifier(EntryIndex entryIndex, InheritanceIndex inheritanceIndex) {
22 super(Enigma.ASM_VERSION, null, null, null, false);
23 this.entryIndex = entryIndex;
24 this.inheritanceIndex = inheritanceIndex;
25 }
26
27 @Override
28 protected boolean isSubTypeOf(BasicValue value, BasicValue expected) {
29 Type expectedType = expected.getType();
30 Type type = value.getType();
31 switch (expectedType.getSort()) {
32 case Type.INT:
33 case Type.FLOAT:
34 case Type.LONG:
35 case Type.DOUBLE:
36 return type.equals(expectedType);
37 case Type.ARRAY:
38 case Type.OBJECT:
39 if (type.equals(NULL_TYPE)) {
40 return true;
41 } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
42 if (isAssignableFrom(expectedType, type)) {
43 return true;
44 } else if (isInterface(expectedType)) {
45 return isAssignableFrom(OBJECT_TYPE, type);
46 } else {
47 return false;
48 }
49 } else {
50 return false;
51 }
52 default:
53 throw new AssertionError();
54 }
55 }
56
57 @Override
58 protected boolean isInterface(Type type) {
59 AccessFlags classAccess = entryIndex.getClassAccess(new ClassEntry(type.getInternalName()));
60
61 if (classAccess != null) {
62 return classAccess.isInterface();
63 }
64
65 Class<?> clazz = getClass(type);
66
67 if (clazz != null) {
68 return clazz.isInterface();
69 }
70
71 return false;
72 }
73
74 @Override
75 protected Type getSuperClass(Type type) {
76 ClassDefEntry definition = entryIndex.getDefinition(new ClassEntry(type.getInternalName()));
77
78 if (definition != null) {
79 return Type.getType('L' + definition.getSuperClass().getFullName() + ';');
80 }
81
82 Class<?> clazz = getClass(type);
83
84 if (clazz != null) {
85 return Type.getType(clazz.getSuperclass());
86 }
87
88 return OBJECT_TYPE;
89 }
90
91 @Override
92 protected boolean isAssignableFrom(Type type1, Type type2) {
93 if (type1.equals(type2)) {
94 return true;
95 }
96
97 if (type2.equals(NULL_TYPE)) {
98 return true;
99 }
100
101 if (type1.getSort() == Type.ARRAY) {
102 return type2.getSort() == Type.ARRAY && isAssignableFrom(Type.getType(type1.getDescriptor().substring(1)), Type.getType(type2.getDescriptor().substring(1)));
103 }
104
105 if (type2.getSort() == Type.ARRAY) {
106 return type1.equals(OBJECT_TYPE);
107 }
108
109 if (type1.getSort() == Type.OBJECT && type2.getSort() == Type.OBJECT) {
110 if (type1.equals(OBJECT_TYPE)) {
111 return true;
112 }
113
114 ClassEntry class1 = new ClassEntry(type1.getInternalName());
115 ClassEntry class2 = new ClassEntry(type2.getInternalName());
116
117 if (entryIndex.hasClass(class1) && entryIndex.hasClass(class2)) {
118 return inheritanceIndex.getAncestors(class2).contains(class1);
119 }
120
121 Class<?> class1Class = getClass(Type.getType('L' + class1.getFullName() + ';'));
122 Class<?> class2Class = getClass(Type.getType('L' + class2.getFullName() + ';'));
123
124 if (class1Class == null) {
125 return true; // missing classes to find out
126 }
127
128 if (class2Class != null) {
129 return class1Class.isAssignableFrom(class2Class);
130 }
131
132 if (entryIndex.hasClass(class2)) {
133 Set<ClassEntry> ancestors = inheritanceIndex.getAncestors(class2);
134
135 for (ClassEntry ancestorEntry : ancestors) {
136 Class<?> ancestor = getClass(Type.getType('L' + ancestorEntry.getFullName() + ';'));
137
138 if (ancestor == null || class1Class.isAssignableFrom(ancestor)) {
139 return true; // assignable, or missing classes to find out
140 }
141 }
142
143 return false;
144 }
145
146 return true; // missing classes to find out
147 }
148
149 return false;
150 }
151
152 @Override
153 protected final Class<?> getClass(Type type) {
154 try {
155 return Class.forName(type.getSort() == Type.ARRAY ? type.getDescriptor().replace('/', '.') : type.getClassName(), false, null);
156 } catch (ClassNotFoundException e) {
157 return null;
158 }
159 }
160}
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 @@
1package cuchaz.enigma.analysis;
2
3import java.util.List;
4import java.util.Objects;
5
6import org.objectweb.asm.Type;
7import org.objectweb.asm.tree.AbstractInsnNode;
8import org.objectweb.asm.tree.analysis.AnalyzerException;
9import org.objectweb.asm.tree.analysis.Interpreter;
10import org.objectweb.asm.tree.analysis.Value;
11
12import cuchaz.enigma.Enigma;
13
14public class InterpreterPair<V extends Value, W extends Value> extends Interpreter<InterpreterPair.PairValue<V, W>> {
15 private final Interpreter<V> left;
16 private final Interpreter<W> right;
17
18 public InterpreterPair(Interpreter<V> left, Interpreter<W> right) {
19 super(Enigma.ASM_VERSION);
20 this.left = left;
21 this.right = right;
22 }
23
24 @Override
25 public PairValue<V, W> newValue(Type type) {
26 return pair(left.newValue(type), right.newValue(type));
27 }
28
29 @Override
30 public PairValue<V, W> newOperation(AbstractInsnNode insn) throws AnalyzerException {
31 return pair(left.newOperation(insn), right.newOperation(insn));
32 }
33
34 @Override
35 public PairValue<V, W> copyOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException {
36 return pair(left.copyOperation(insn, value.left), right.copyOperation(insn, value.right));
37 }
38
39 @Override
40 public PairValue<V, W> unaryOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException {
41 return pair(left.unaryOperation(insn, value.left), right.unaryOperation(insn, value.right));
42 }
43
44 @Override
45 public PairValue<V, W> binaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2) throws AnalyzerException {
46 return pair(left.binaryOperation(insn, value1.left, value2.left), right.binaryOperation(insn, value1.right, value2.right));
47 }
48
49 @Override
50 public PairValue<V, W> ternaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2, PairValue<V, W> value3) throws AnalyzerException {
51 return pair(left.ternaryOperation(insn, value1.left, value2.left, value3.left), right.ternaryOperation(insn, value1.right, value2.right, value3.right));
52 }
53
54 @Override
55 public PairValue<V, W> naryOperation(AbstractInsnNode insn, List<? extends PairValue<V, W>> values) throws AnalyzerException {
56 return pair(left.naryOperation(insn, values.stream().map(v -> v.left).toList()), right.naryOperation(insn, values.stream().map(v -> v.right).toList()));
57 }
58
59 @Override
60 public void returnOperation(AbstractInsnNode insn, PairValue<V, W> value, PairValue<V, W> expected) throws AnalyzerException {
61 left.returnOperation(insn, value.left, expected.left);
62 right.returnOperation(insn, value.right, expected.right);
63 }
64
65 @Override
66 public PairValue<V, W> merge(PairValue<V, W> value1, PairValue<V, W> value2) {
67 return pair(left.merge(value1.left, value2.left), right.merge(value1.right, value2.right));
68 }
69
70 private PairValue<V, W> pair(V left, W right) {
71 if (left == null && right == null) {
72 return null;
73 }
74
75 return new PairValue<>(left, right);
76 }
77
78 public static final class PairValue<V extends Value, W extends Value> implements Value {
79 public final V left;
80 public final W right;
81
82 public PairValue(V left, W right) {
83 if (left == null && right == null) {
84 throw new IllegalArgumentException("should use null rather than pair of nulls");
85 }
86
87 this.left = left;
88 this.right = right;
89 }
90
91 @Override
92 public boolean equals(Object o) {
93 return o instanceof InterpreterPair.PairValue pairValue && Objects.equals(left, pairValue.left) && Objects.equals(right, pairValue.right);
94 }
95
96 @Override
97 public int hashCode() {
98 return left.hashCode() * 31 + right.hashCode();
99 }
100
101 @Override
102 public int getSize() {
103 return (left == null ? right : left).getSize();
104 }
105 }
106}
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 @@
1package cuchaz.enigma.analysis;
2
3import java.util.function.Consumer;
4
5import org.objectweb.asm.tree.MethodNode;
6
7public class MethodNodeWithAction extends MethodNode {
8 private final Consumer<MethodNode> action;
9
10 public MethodNodeWithAction(int api, int access, String name, String descriptor, String signature, String[] exceptions, Consumer<MethodNode> action) {
11 super(api, access, name, descriptor, signature, exceptions);
12 this.action = action;
13 }
14
15 @Override
16 public void visitEnd() {
17 action.accept(this);
18 }
19}
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 {
42 } 42 }
43 43
44 public void load(EnigmaProject project, StructureTreeOptions options) { 44 public void load(EnigmaProject project, StructureTreeOptions options) {
45 Stream<ParentedEntry> children = project.getJarIndex().getChildrenByClass().get(this.parentEntry).stream(); 45 Stream<ParentedEntry<?>> children = project.getJarIndex().getChildrenByClass().get(this.parentEntry).stream();
46 46
47 children = switch (options.obfuscationVisibility()) { 47 children = switch (options.obfuscationVisibility()) {
48 case ALL -> children; 48 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;
5import java.util.HashMap; 5import java.util.HashMap;
6import java.util.List; 6import java.util.List;
7import java.util.Map; 7import java.util.Map;
8import java.util.concurrent.ConcurrentHashMap;
9import java.util.concurrent.ConcurrentMap;
8 10
9import javax.annotation.Nullable; 11import javax.annotation.Nullable;
10 12
11import com.google.common.collect.Maps;
12
13import cuchaz.enigma.translation.representation.AccessFlags; 13import cuchaz.enigma.translation.representation.AccessFlags;
14import cuchaz.enigma.translation.representation.MethodDescriptor; 14import cuchaz.enigma.translation.representation.MethodDescriptor;
15import cuchaz.enigma.translation.representation.TypeDescriptor; 15import cuchaz.enigma.translation.representation.TypeDescriptor;
@@ -22,8 +22,8 @@ public class BridgeMethodIndex implements JarIndexer {
22 private final InheritanceIndex inheritanceIndex; 22 private final InheritanceIndex inheritanceIndex;
23 private final ReferenceIndex referenceIndex; 23 private final ReferenceIndex referenceIndex;
24 24
25 private final Map<MethodEntry, MethodEntry> bridgeToSpecialized = Maps.newHashMap(); 25 private final ConcurrentMap<MethodEntry, MethodEntry> bridgeToSpecialized = new ConcurrentHashMap<>();
26 private final Map<MethodEntry, MethodEntry> specializedToBridge = Maps.newHashMap(); 26 private final ConcurrentMap<MethodEntry, MethodEntry> specializedToBridge = new ConcurrentHashMap<>();
27 27
28 public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) { 28 public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) {
29 this.entryIndex = entryIndex; 29 this.entryIndex = entryIndex;
@@ -50,17 +50,17 @@ public class BridgeMethodIndex implements JarIndexer {
50 public void processIndex(JarIndex index) { 50 public void processIndex(JarIndex index) {
51 Map<MethodEntry, MethodEntry> copiedAccessToBridge = new HashMap<>(specializedToBridge); 51 Map<MethodEntry, MethodEntry> copiedAccessToBridge = new HashMap<>(specializedToBridge);
52 52
53 for (Map.Entry<MethodEntry, MethodEntry> entry : copiedAccessToBridge.entrySet()) { 53 copiedAccessToBridge.entrySet().parallelStream().forEach(entry -> {
54 MethodEntry specializedEntry = entry.getKey(); 54 MethodEntry specializedEntry = entry.getKey();
55 MethodEntry bridgeEntry = entry.getValue(); 55 MethodEntry bridgeEntry = entry.getValue();
56 56
57 if (bridgeEntry.getName().equals(specializedEntry.getName())) { 57 if (bridgeEntry.getName().equals(specializedEntry.getName())) {
58 continue; 58 return;
59 } 59 }
60 60
61 MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName()); 61 MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName());
62 specializedToBridge.put(renamedSpecializedEntry, specializedToBridge.get(specializedEntry)); 62 specializedToBridge.put(renamedSpecializedEntry, copiedAccessToBridge.get(specializedEntry));
63 } 63 });
64 } 64 }
65 65
66 private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) { 66 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 @@
1package cuchaz.enigma.analysis.index; 1package cuchaz.enigma.analysis.index;
2 2
3import java.util.Collection; 3import java.util.Collection;
4import java.util.HashMap; 4import java.util.concurrent.ConcurrentHashMap;
5import java.util.Map; 5import java.util.concurrent.ConcurrentMap;
6 6
7import javax.annotation.Nullable; 7import javax.annotation.Nullable;
8 8
@@ -17,10 +17,10 @@ import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
17import cuchaz.enigma.translation.representation.entry.MethodEntry; 17import cuchaz.enigma.translation.representation.entry.MethodEntry;
18 18
19public class EntryIndex implements JarIndexer { 19public class EntryIndex implements JarIndexer {
20 private Map<ClassEntry, AccessFlags> classes = new HashMap<>(); 20 private final ConcurrentMap<ClassEntry, AccessFlags> classes = new ConcurrentHashMap<>();
21 private Map<FieldEntry, AccessFlags> fields = new HashMap<>(); 21 private final ConcurrentMap<FieldEntry, AccessFlags> fields = new ConcurrentHashMap<>();
22 private Map<MethodEntry, AccessFlags> methods = new HashMap<>(); 22 private final ConcurrentMap<MethodEntry, AccessFlags> methods = new ConcurrentHashMap<>();
23 private Map<ClassEntry, ClassDefEntry> definitions = new HashMap<>(); 23 private final ConcurrentMap<ClassEntry, ClassDefEntry> definitions = new ConcurrentHashMap<>();
24 24
25 @Override 25 @Override
26 public void indexClass(ClassDefEntry classEntry) { 26 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;
4 4
5import org.objectweb.asm.ClassVisitor; 5import org.objectweb.asm.ClassVisitor;
6import org.objectweb.asm.Handle; 6import org.objectweb.asm.Handle;
7import org.objectweb.asm.Label;
7import org.objectweb.asm.MethodVisitor; 8import org.objectweb.asm.MethodVisitor;
8import org.objectweb.asm.Opcodes; 9import org.objectweb.asm.Opcodes;
9import org.objectweb.asm.Type; 10import org.objectweb.asm.Type;
10import org.objectweb.asm.tree.AbstractInsnNode; 11
11import org.objectweb.asm.tree.FieldInsnNode; 12import cuchaz.enigma.analysis.BetterAnalyzerAdapter;
12import org.objectweb.asm.tree.InvokeDynamicInsnNode;
13import org.objectweb.asm.tree.LdcInsnNode;
14import org.objectweb.asm.tree.MethodInsnNode;
15import org.objectweb.asm.tree.TypeInsnNode;
16import org.objectweb.asm.tree.analysis.Analyzer;
17import org.objectweb.asm.tree.analysis.AnalyzerException;
18import org.objectweb.asm.tree.analysis.BasicValue;
19import org.objectweb.asm.tree.analysis.SourceInterpreter;
20import org.objectweb.asm.tree.analysis.SourceValue;
21
22import cuchaz.enigma.analysis.IndexSimpleVerifier;
23import cuchaz.enigma.analysis.InterpreterPair;
24import cuchaz.enigma.analysis.MethodNodeWithAction;
25import cuchaz.enigma.analysis.ReferenceTargetType; 13import cuchaz.enigma.analysis.ReferenceTargetType;
26import cuchaz.enigma.translation.representation.AccessFlags; 14import cuchaz.enigma.translation.representation.AccessFlags;
27import cuchaz.enigma.translation.representation.Lambda; 15import cuchaz.enigma.translation.representation.Lambda;
@@ -35,16 +23,12 @@ import cuchaz.enigma.translation.representation.entry.ParentedEntry;
35 23
36public class IndexReferenceVisitor extends ClassVisitor { 24public class IndexReferenceVisitor extends ClassVisitor {
37 private final JarIndexer indexer; 25 private final JarIndexer indexer;
38 private final EntryIndex entryIndex;
39 private final InheritanceIndex inheritanceIndex;
40 private ClassEntry classEntry; 26 private ClassEntry classEntry;
41 private String className; 27 private String className;
42 28
43 public IndexReferenceVisitor(JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex, int api) { 29 public IndexReferenceVisitor(JarIndexer indexer, int api) {
44 super(api); 30 super(api);
45 this.indexer = indexer; 31 this.indexer = indexer;
46 this.entryIndex = entryIndex;
47 this.inheritanceIndex = inheritanceIndex;
48 } 32 }
49 33
50 @Override 34 @Override
@@ -56,153 +40,161 @@ public class IndexReferenceVisitor extends ClassVisitor {
56 @Override 40 @Override
57 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 41 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
58 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); 42 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
59 return new MethodNodeWithAction(api, access, name, desc, signature, exceptions, methodNode -> { 43 return new IndexReferenceMethodVisitor(api, className, access, name, desc, entry, indexer);
60 try {
61 new Analyzer<>(new MethodInterpreter(entry, indexer, entryIndex, inheritanceIndex)).analyze(className, methodNode);
62 } catch (AnalyzerException e) {
63 throw new RuntimeException(e);
64 }
65 });
66 } 44 }
67 45
68 private static class MethodInterpreter extends InterpreterPair<BasicValue, SourceValue> { 46 private static class IndexReferenceMethodVisitor extends BetterAnalyzerAdapter {
69 private final MethodDefEntry callerEntry; 47 private final MethodDefEntry callerEntry;
70 private final JarIndexer indexer; 48 private final JarIndexer indexer;
71 49
72 MethodInterpreter(MethodDefEntry callerEntry, JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex) { 50 IndexReferenceMethodVisitor(int api, String owner, int access, String name, String descriptor, MethodDefEntry callerEntry, JarIndexer indexer) {
73 super(new IndexSimpleVerifier(entryIndex, inheritanceIndex), new SourceInterpreter()); 51 super(api, owner, access, name, descriptor, null);
74 this.callerEntry = callerEntry; 52 this.callerEntry = callerEntry;
75 this.indexer = indexer; 53 this.indexer = indexer;
76 } 54 }
77 55
78 @Override 56 @Override
79 public PairValue<BasicValue, SourceValue> newOperation(AbstractInsnNode insn) throws AnalyzerException { 57 public void visitFieldInsn(int opcode, String owner, String name, String descriptor) {
80 if (insn.getOpcode() == Opcodes.GETSTATIC) { 58 switch (opcode) {
81 FieldInsnNode field = (FieldInsnNode) insn; 59 case Opcodes.GETSTATIC, Opcodes.PUTSTATIC -> indexer.indexFieldReference(callerEntry, FieldEntry.parse(owner, name, descriptor), ReferenceTargetType.none());
82 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none()); 60 case Opcodes.GETFIELD -> indexer.indexFieldReference(callerEntry, FieldEntry.parse(owner, name, descriptor), getReferenceTargetType(0));
61 case Opcodes.PUTFIELD -> indexer.indexFieldReference(callerEntry, FieldEntry.parse(owner, name, descriptor), getReferenceTargetType(Type.getType(descriptor).getSize()));
83 } 62 }
84 63
85 if (insn.getOpcode() == Opcodes.LDC) { 64 super.visitFieldInsn(opcode, owner, name, descriptor);
86 LdcInsnNode ldc = (LdcInsnNode) insn; 65 }
87 66
88 if (ldc.getType() == Type.ARRAY && ldc.cst instanceof Type type) { 67 @Override
89 String className = type.getClassName().replace(".", "/"); 68 public void visitLdcInsn(Object value) {
90 indexer.indexClassReference(callerEntry, ClassEntry.parse(className), ReferenceTargetType.none()); 69 if (value instanceof Type type && (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY)) {
70 if (type.getSort() == Type.ARRAY) {
71 type = type.getElementType();
91 } 72 }
73
74 indexer.indexClassReference(callerEntry, ClassEntry.parse(type.getInternalName()), ReferenceTargetType.none());
92 } 75 }
93 76
94 return super.newOperation(insn); 77 super.visitLdcInsn(value);
95 } 78 }
96 79
97 @Override 80 @Override
98 public PairValue<BasicValue, SourceValue> unaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value) throws AnalyzerException { 81 public void visitTypeInsn(int opcode, String type) {
99 if (insn.getOpcode() == Opcodes.PUTSTATIC) { 82 if (opcode == Opcodes.INSTANCEOF || opcode == Opcodes.CHECKCAST) {
100 FieldInsnNode field = (FieldInsnNode) insn; 83 Type classType = Type.getObjectType(type);
101 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none());
102 }
103
104 if (insn.getOpcode() == Opcodes.GETFIELD) {
105 FieldInsnNode field = (FieldInsnNode) insn;
106 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), getReferenceTargetType(value, insn));
107 }
108 84
109 if (insn.getOpcode() == Opcodes.INSTANCEOF) { 85 if (classType.getSort() == Type.ARRAY) {
110 TypeInsnNode type = (TypeInsnNode) insn; 86 classType = classType.getElementType();
111 // Note: type.desc is actually the name 87 }
112 indexer.indexClassReference(callerEntry, ClassEntry.parse(type.desc), ReferenceTargetType.none());
113 }
114 88
115 if (insn.getOpcode() == Opcodes.CHECKCAST) { 89 indexer.indexClassReference(callerEntry, ClassEntry.parse(classType.getInternalName()), ReferenceTargetType.none());
116 TypeInsnNode type = (TypeInsnNode) insn;
117 indexer.indexClassReference(callerEntry, ClassEntry.parse(type.desc), ReferenceTargetType.none());
118 } 90 }
119 91
120 return super.unaryOperation(insn, value); 92 super.visitTypeInsn(opcode, type);
121 } 93 }
122 94
123 @Override 95 @Override
124 public PairValue<BasicValue, SourceValue> binaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value1, PairValue<BasicValue, SourceValue> value2) throws AnalyzerException { 96 public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
125 if (insn.getOpcode() == Opcodes.PUTFIELD) { 97 ReferenceTargetType targetType;
126 FieldInsnNode field = (FieldInsnNode) insn; 98
127 FieldEntry fieldEntry = FieldEntry.parse(field.owner, field.name, field.desc); 99 if (opcode == Opcodes.INVOKESTATIC) {
128 indexer.indexFieldReference(callerEntry, fieldEntry, ReferenceTargetType.none()); 100 targetType = ReferenceTargetType.none();
101 } else {
102 int argSize = (Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1;
103 targetType = getReferenceTargetType(argSize);
129 } 104 }
130 105
131 return super.binaryOperation(insn, value1, value2); 106 indexer.indexMethodReference(callerEntry, MethodEntry.parse(owner, name, descriptor), targetType);
107
108 super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
132 } 109 }
133 110
134 @Override 111 @Override
135 public PairValue<BasicValue, SourceValue> naryOperation(AbstractInsnNode insn, List<? extends PairValue<BasicValue, SourceValue>> values) throws AnalyzerException { 112 public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) {
136 if (insn.getOpcode() == Opcodes.INVOKEINTERFACE || insn.getOpcode() == Opcodes.INVOKESPECIAL || insn.getOpcode() == Opcodes.INVOKEVIRTUAL) { 113 if ("java/lang/invoke/LambdaMetafactory".equals(bootstrapMethodHandle.getOwner()) && ("metafactory".equals(bootstrapMethodHandle.getName()) || "altMetafactory".equals(bootstrapMethodHandle.getName()))) {
137 MethodInsnNode methodInsn = (MethodInsnNode) insn; 114 Type samMethodType = (Type) bootstrapMethodArguments[0];
138 indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), getReferenceTargetType(values.get(0), insn)); 115 Handle implMethod = (Handle) bootstrapMethodArguments[1];
139 } 116 Type instantiatedMethodType = (Type) bootstrapMethodArguments[2];
140 117
141 if (insn.getOpcode() == Opcodes.INVOKESTATIC) { 118 ReferenceTargetType targetType;
142 MethodInsnNode methodInsn = (MethodInsnNode) insn; 119
143 indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), ReferenceTargetType.none()); 120 if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) {
144 } 121 if (instantiatedMethodType.getArgumentCount() < Type.getArgumentCount(implMethod.getDesc())) {
145 122 if (descriptor.startsWith("(L")) { // is the first parameter of the indy an object type?
146 if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC) { 123 int argSize = (Type.getArgumentsAndReturnSizes(descriptor) >> 2) - 1;
147 InvokeDynamicInsnNode invokeDynamicInsn = (InvokeDynamicInsnNode) insn; 124 targetType = getReferenceTargetType(argSize - 1);
148 List<AbstractInsnNode> args = values.stream().map(v -> v.right.insns.stream().findFirst().orElseThrow(AssertionError::new)).toList();
149
150 if ("java/lang/invoke/LambdaMetafactory".equals(invokeDynamicInsn.bsm.getOwner()) && "metafactory".equals(invokeDynamicInsn.bsm.getName())) {
151 Type samMethodType = (Type) invokeDynamicInsn.bsmArgs[0];
152 Handle implMethod = (Handle) invokeDynamicInsn.bsmArgs[1];
153 Type instantiatedMethodType = (Type) invokeDynamicInsn.bsmArgs[2];
154
155 ReferenceTargetType targetType;
156
157 if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) {
158 if (instantiatedMethodType.getArgumentTypes().length < Type.getArgumentTypes(implMethod.getDesc()).length) {
159 targetType = getReferenceTargetType(values.get(0), insn);
160 } else { 125 } else {
161 targetType = ReferenceTargetType.none(); // no "this" argument 126 targetType = ReferenceTargetType.none();
162 } 127 }
163 } else { 128 } else {
164 targetType = ReferenceTargetType.none(); 129 targetType = ReferenceTargetType.none(); // no "this" argument
165 } 130 }
166 131 } else {
167 indexer.indexLambda(callerEntry, new Lambda(invokeDynamicInsn.name, new MethodDescriptor(invokeDynamicInsn.desc), new MethodDescriptor(samMethodType.getDescriptor()), getHandleEntry(implMethod), new MethodDescriptor(instantiatedMethodType.getDescriptor())), targetType); 132 targetType = ReferenceTargetType.none();
168 } 133 }
134
135 indexer.indexLambda(callerEntry, new Lambda(name, new MethodDescriptor(descriptor), new MethodDescriptor(samMethodType.getDescriptor()), getHandleEntry(implMethod), new MethodDescriptor(instantiatedMethodType.getDescriptor())), targetType);
169 } 136 }
170 137
171 return super.naryOperation(insn, values); 138 super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
172 } 139 }
173 140
174 private ReferenceTargetType getReferenceTargetType(PairValue<BasicValue, SourceValue> target, AbstractInsnNode insn) throws AnalyzerException { 141 private ReferenceTargetType getReferenceTargetType(int stackDepth) {
175 if (target.left == BasicValue.UNINITIALIZED_VALUE) { 142 if (stackDepth >= stack.size()) {
143 throw new IllegalStateException("Stack depth " + stackDepth + " is higher than the stack: " + stackValuesToString(stack) + " in method " + callerEntry);
144 }
145
146 Object stackValue = stack.get(stack.size() - 1 - stackDepth);
147
148 if (stackValue.equals(Opcodes.UNINITIALIZED_THIS) || stackValue instanceof Label) {
176 return ReferenceTargetType.uninitialized(); 149 return ReferenceTargetType.uninitialized();
177 } 150 }
178 151
179 if (target.left.getType().getSort() == Type.OBJECT) { 152 if (!(stackValue instanceof String type)) {
180 return ReferenceTargetType.classType(new ClassEntry(target.left.getType().getInternalName())); 153 throw new IllegalStateException("Illegal stack value in method " + callerEntry + ": " + stackValuesToString(List.of(stackValue)));
181 } 154 }
182 155
183 if (target.left.getType().getSort() == Type.ARRAY) { 156 if (type.startsWith("[")) {
157 // array type
184 return ReferenceTargetType.classType(new ClassEntry("java/lang/Object")); 158 return ReferenceTargetType.classType(new ClassEntry("java/lang/Object"));
159 } else {
160 return ReferenceTargetType.classType(new ClassEntry(type));
185 } 161 }
186
187 throw new AnalyzerException(insn, "called method on or accessed field of non-object type");
188 } 162 }
189 163
190 private static ParentedEntry<?> getHandleEntry(Handle handle) { 164 private static String stackValuesToString(List<Object> stack) {
191 switch (handle.getTag()) { 165 StringBuilder result = new StringBuilder("[");
192 case Opcodes.H_GETFIELD: 166 boolean first = true;
193 case Opcodes.H_GETSTATIC: 167
194 case Opcodes.H_PUTFIELD: 168 for (Object stackValue : stack) {
195 case Opcodes.H_PUTSTATIC: 169 if (first) {
196 return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); 170 first = false;
197 case Opcodes.H_INVOKEINTERFACE: 171 } else {
198 case Opcodes.H_INVOKESPECIAL: 172 result.append(", ");
199 case Opcodes.H_INVOKESTATIC: 173 }
200 case Opcodes.H_INVOKEVIRTUAL: 174
201 case Opcodes.H_NEWINVOKESPECIAL: 175 if (stackValue instanceof String str) {
202 return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); 176 result.append(str);
177 } else if (stackValue instanceof Integer i) {
178 result.append("TIFDJNU".charAt(i));
179 } else if (stackValue instanceof Label) {
180 result.append('U');
181 } else {
182 throw new AssertionError("Illegal stack value type: " + stackValue.getClass().getName());
183 }
203 } 184 }
204 185
205 throw new RuntimeException("Invalid handle tag " + handle.getTag()); 186 return result.append(']').toString();
187 }
188
189 private static ParentedEntry<?> getHandleEntry(Handle handle) {
190 return switch (handle.getTag()) {
191 case Opcodes.H_GETFIELD, Opcodes.H_GETSTATIC, Opcodes.H_PUTFIELD, Opcodes.H_PUTSTATIC ->
192 FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
193 case Opcodes.H_INVOKEINTERFACE, Opcodes.H_INVOKESPECIAL, Opcodes.H_INVOKESTATIC,
194 Opcodes.H_INVOKEVIRTUAL, Opcodes.H_NEWINVOKESPECIAL ->
195 MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
196 default -> throw new RuntimeException("Invalid handle tag " + handle.getTag());
197 };
206 } 198 }
207 } 199 }
208} 200}
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 @@
11 11
12package cuchaz.enigma.analysis.index; 12package cuchaz.enigma.analysis.index;
13 13
14import java.util.ArrayList;
14import java.util.Collection; 15import java.util.Collection;
16import java.util.Collections;
15import java.util.HashSet; 17import java.util.HashSet;
16import java.util.LinkedList; 18import java.util.LinkedList;
19import java.util.List;
17import java.util.Set; 20import java.util.Set;
21import java.util.concurrent.ConcurrentHashMap;
22import java.util.concurrent.ConcurrentMap;
18 23
19import com.google.common.collect.HashMultimap;
20import com.google.common.collect.Multimap;
21import com.google.common.collect.Sets; 24import com.google.common.collect.Sets;
22 25
23import cuchaz.enigma.translation.representation.entry.ClassDefEntry; 26import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
@@ -26,8 +29,8 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry;
26public class InheritanceIndex implements JarIndexer { 29public class InheritanceIndex implements JarIndexer {
27 private final EntryIndex entryIndex; 30 private final EntryIndex entryIndex;
28 31
29 private Multimap<ClassEntry, ClassEntry> classParents = HashMultimap.create(); 32 private final ConcurrentMap<ClassEntry, List<ClassEntry>> classParents = new ConcurrentHashMap<>();
30 private Multimap<ClassEntry, ClassEntry> classChildren = HashMultimap.create(); 33 private final ConcurrentMap<ClassEntry, List<ClassEntry>> classChildren = new ConcurrentHashMap<>();
31 34
32 public InheritanceIndex(EntryIndex entryIndex) { 35 public InheritanceIndex(EntryIndex entryIndex) {
33 this.entryIndex = entryIndex; 36 this.entryIndex = entryIndex;
@@ -51,16 +54,18 @@ public class InheritanceIndex implements JarIndexer {
51 } 54 }
52 55
53 private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) { 56 private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) {
54 classParents.put(childEntry, parentEntry); 57 // No need to add to classParents in a synchronized way, as we'll be the only ones adding to the corresponding childEntry
55 classChildren.put(parentEntry, childEntry); 58 classParents.computeIfAbsent(childEntry, k -> new ArrayList<>()).add(parentEntry);
59
60 JarIndex.synchronizedAdd(classChildren, parentEntry, childEntry);
56 } 61 }
57 62
58 public Collection<ClassEntry> getParents(ClassEntry classEntry) { 63 public Collection<ClassEntry> getParents(ClassEntry classEntry) {
59 return classParents.get(classEntry); 64 return classParents.getOrDefault(classEntry, Collections.emptyList());
60 } 65 }
61 66
62 public Collection<ClassEntry> getChildren(ClassEntry classEntry) { 67 public Collection<ClassEntry> getChildren(ClassEntry classEntry) {
63 return classChildren.get(classEntry); 68 return classChildren.getOrDefault(classEntry, Collections.emptyList());
64 } 69 }
65 70
66 public Collection<ClassEntry> getDescendants(ClassEntry classEntry) { 71 public Collection<ClassEntry> 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 @@
11 11
12package cuchaz.enigma.analysis.index; 12package cuchaz.enigma.analysis.index;
13 13
14import java.util.ArrayList;
14import java.util.Collection; 15import java.util.Collection;
15import java.util.HashSet; 16import java.util.HashSet;
16import java.util.List; 17import java.util.List;
18import java.util.Map;
17import java.util.Set; 19import java.util.Set;
18 20import java.util.concurrent.ConcurrentHashMap;
19import com.google.common.collect.ArrayListMultimap; 21import java.util.concurrent.ConcurrentMap;
20import com.google.common.collect.HashMultimap;
21import com.google.common.collect.ListMultimap;
22import com.google.common.collect.Multimap;
23 22
24import cuchaz.enigma.Enigma; 23import cuchaz.enigma.Enigma;
25import cuchaz.enigma.ProgressListener; 24import cuchaz.enigma.ProgressListener;
26import cuchaz.enigma.analysis.ReferenceTargetType; 25import cuchaz.enigma.analysis.ReferenceTargetType;
26import cuchaz.enigma.classprovider.AddFramesIfNecessaryClassProvider;
27import cuchaz.enigma.classprovider.CachingClassProvider;
27import cuchaz.enigma.classprovider.ClassProvider; 28import cuchaz.enigma.classprovider.ClassProvider;
28import cuchaz.enigma.translation.mapping.EntryResolver; 29import cuchaz.enigma.translation.mapping.EntryResolver;
29import cuchaz.enigma.translation.mapping.IndexEntryResolver; 30import cuchaz.enigma.translation.mapping.IndexEntryResolver;
@@ -48,8 +49,7 @@ public class JarIndex implements JarIndexer {
48 49
49 private final Collection<JarIndexer> indexers; 50 private final Collection<JarIndexer> indexers;
50 51
51 private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create(); 52 private final ConcurrentMap<ClassEntry, List<ParentedEntry<?>>> childrenByClass;
52 private final ListMultimap<ClassEntry, ParentedEntry> childrenByClass;
53 53
54 public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { 54 public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) {
55 this.entryIndex = entryIndex; 55 this.entryIndex = entryIndex;
@@ -59,7 +59,7 @@ public class JarIndex implements JarIndexer {
59 this.packageVisibilityIndex = packageVisibilityIndex; 59 this.packageVisibilityIndex = packageVisibilityIndex;
60 this.indexers = List.of(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); 60 this.indexers = List.of(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
61 this.entryResolver = new IndexEntryResolver(this); 61 this.entryResolver = new IndexEntryResolver(this);
62 this.childrenByClass = ArrayListMultimap.create(); 62 this.childrenByClass = new ConcurrentHashMap<>();
63 } 63 }
64 64
65 public static JarIndex empty() { 65 public static JarIndex empty() {
@@ -71,31 +71,35 @@ public class JarIndex implements JarIndexer {
71 return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); 71 return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
72 } 72 }
73 73
74 public void indexJar(Set<String> classNames, ClassProvider classProvider, ProgressListener progress) { 74 public ClassProvider indexJar(Set<String> classNames, ClassProvider classProvider, ProgressListener progress) {
75 indexedClasses.addAll(classNames); 75 indexedClasses.addAll(classNames);
76 progress.init(4, I18n.translate("progress.jar.indexing")); 76 progress.init(4, I18n.translate("progress.jar.indexing"));
77 77
78 progress.step(1, I18n.translate("progress.jar.indexing.entries")); 78 progress.step(1, I18n.translate("progress.jar.indexing.entries"));
79 79
80 for (String className : classNames) { 80 classNames.parallelStream().forEach(className -> {
81 classProvider.get(className).accept(new IndexClassVisitor(this, Enigma.ASM_VERSION)); 81 classProvider.get(className).accept(new IndexClassVisitor(this, Enigma.ASM_VERSION));
82 } 82 });
83
84 ClassProvider classProviderWithFrames = new CachingClassProvider(new AddFramesIfNecessaryClassProvider(classProvider, entryIndex));
83 85
84 progress.step(2, I18n.translate("progress.jar.indexing.references")); 86 progress.step(2, I18n.translate("progress.jar.indexing.references"));
85 87
86 for (String className : classNames) { 88 classNames.parallelStream().forEach(className -> {
87 try { 89 try {
88 classProvider.get(className).accept(new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION)); 90 classProviderWithFrames.get(className).accept(new IndexReferenceVisitor(this, Enigma.ASM_VERSION));
89 } catch (Exception e) { 91 } catch (Exception e) {
90 throw new RuntimeException("Exception while indexing class: " + className, e); 92 throw new RuntimeException("Exception while indexing class: " + className, e);
91 } 93 }
92 } 94 });
93 95
94 progress.step(3, I18n.translate("progress.jar.indexing.methods")); 96 progress.step(3, I18n.translate("progress.jar.indexing.methods"));
95 bridgeMethodIndex.findBridgeMethods(); 97 bridgeMethodIndex.findBridgeMethods();
96 98
97 progress.step(4, I18n.translate("progress.jar.indexing.process")); 99 progress.step(4, I18n.translate("progress.jar.indexing.process"));
98 processIndex(this); 100 processIndex(this);
101
102 return classProviderWithFrames;
99 } 103 }
100 104
101 @Override 105 @Override
@@ -118,7 +122,7 @@ public class JarIndex implements JarIndexer {
118 indexers.forEach(indexer -> indexer.indexClass(classEntry)); 122 indexers.forEach(indexer -> indexer.indexClass(classEntry));
119 123
120 if (classEntry.isInnerClass() && !classEntry.getAccess().isSynthetic()) { 124 if (classEntry.isInnerClass() && !classEntry.getAccess().isSynthetic()) {
121 childrenByClass.put(classEntry.getParent(), classEntry); 125 synchronizedAdd(childrenByClass, classEntry.getParent(), classEntry);
122 } 126 }
123 } 127 }
124 128
@@ -131,7 +135,7 @@ public class JarIndex implements JarIndexer {
131 indexers.forEach(indexer -> indexer.indexField(fieldEntry)); 135 indexers.forEach(indexer -> indexer.indexField(fieldEntry));
132 136
133 if (!fieldEntry.getAccess().isSynthetic()) { 137 if (!fieldEntry.getAccess().isSynthetic()) {
134 childrenByClass.put(fieldEntry.getParent(), fieldEntry); 138 synchronizedAdd(childrenByClass, fieldEntry.getParent(), fieldEntry);
135 } 139 }
136 } 140 }
137 141
@@ -144,11 +148,7 @@ public class JarIndex implements JarIndexer {
144 indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); 148 indexers.forEach(indexer -> indexer.indexMethod(methodEntry));
145 149
146 if (!methodEntry.getAccess().isSynthetic() && !methodEntry.getName().equals("<clinit>")) { 150 if (!methodEntry.getAccess().isSynthetic() && !methodEntry.getName().equals("<clinit>")) {
147 childrenByClass.put(methodEntry.getParent(), methodEntry); 151 synchronizedAdd(childrenByClass, methodEntry.getParent(), methodEntry);
148 }
149
150 if (!methodEntry.isConstructor()) {
151 methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry);
152 } 152 }
153 } 153 }
154 154
@@ -212,11 +212,18 @@ public class JarIndex implements JarIndexer {
212 return entryResolver; 212 return entryResolver;
213 } 213 }
214 214
215 public ListMultimap<ClassEntry, ParentedEntry> getChildrenByClass() { 215 public Map<ClassEntry, List<ParentedEntry<?>>> getChildrenByClass() {
216 return this.childrenByClass; 216 return this.childrenByClass;
217 } 217 }
218 218
219 public boolean isIndexed(String internalName) { 219 public boolean isIndexed(String internalName) {
220 return indexedClasses.contains(internalName); 220 return indexedClasses.contains(internalName);
221 } 221 }
222
223 static <K, V> void synchronizedAdd(ConcurrentMap<K, List<V>> map, K key, V value) {
224 List<V> list = map.computeIfAbsent(key, k -> new ArrayList<>());
225 synchronized (list) {
226 list.add(value);
227 }
228 }
222} 229}
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;
6import java.util.List; 6import java.util.List;
7import java.util.Map; 7import java.util.Map;
8import java.util.Set; 8import java.util.Set;
9import java.util.concurrent.ConcurrentHashMap;
10import java.util.concurrent.ConcurrentMap;
9 11
10import com.google.common.collect.HashMultimap;
11import com.google.common.collect.Lists; 12import com.google.common.collect.Lists;
12import com.google.common.collect.Maps; 13import com.google.common.collect.Maps;
13import com.google.common.collect.Sets; 14import com.google.common.collect.Sets;
@@ -46,19 +47,25 @@ public class PackageVisibilityIndex implements JarIndexer {
46 return true; 47 return true;
47 } 48 }
48 49
49 private final HashMultimap<ClassEntry, ClassEntry> connections = HashMultimap.create(); 50 private final ConcurrentMap<ClassEntry, List<ClassEntry>> connections = new ConcurrentHashMap<>();
50 private final List<Set<ClassEntry>> partitions = Lists.newArrayList(); 51 private final List<Set<ClassEntry>> partitions = Lists.newArrayList();
51 private final Map<ClassEntry, Set<ClassEntry>> classPartitions = Maps.newHashMap(); 52 private final Map<ClassEntry, Set<ClassEntry>> classPartitions = Maps.newHashMap();
52 53
53 private void addConnection(ClassEntry classA, ClassEntry classB) { 54 private void addConnection(ClassEntry classA, ClassEntry classB) {
54 if (classA != classB) { 55 if (classA != classB) {
55 connections.put(classA, classB); 56 JarIndex.synchronizedAdd(connections, classA, classB);
56 connections.put(classB, classA); 57 JarIndex.synchronizedAdd(connections, classB, classA);
57 } 58 }
58 } 59 }
59 60
60 private void buildPartition(Set<ClassEntry> unassignedClasses, Set<ClassEntry> partition, ClassEntry member) { 61 private void buildPartition(Set<ClassEntry> unassignedClasses, Set<ClassEntry> partition, ClassEntry member) {
61 for (ClassEntry connected : connections.get(member)) { 62 List<ClassEntry> memberConnections = connections.get(member);
63
64 if (memberConnections == null) {
65 return;
66 }
67
68 for (ClassEntry connected : memberConnections) {
62 if (unassignedClasses.remove(connected)) { 69 if (unassignedClasses.remove(connected)) {
63 partition.add(connected); 70 partition.add(connected);
64 buildPartition(unassignedClasses, partition, connected); 71 buildPartition(unassignedClasses, partition, connected);
@@ -67,7 +74,7 @@ public class PackageVisibilityIndex implements JarIndexer {
67 } 74 }
68 75
69 private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) { 76 private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) {
70 for (FieldEntry entry : entryIndex.getFields()) { 77 entryIndex.getFields().parallelStream().forEach(entry -> {
71 AccessFlags entryAcc = entryIndex.getFieldAccess(entry); 78 AccessFlags entryAcc = entryIndex.getFieldAccess(entry);
72 79
73 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { 80 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
@@ -77,9 +84,9 @@ public class PackageVisibilityIndex implements JarIndexer {
77 } 84 }
78 } 85 }
79 } 86 }
80 } 87 });
81 88
82 for (MethodEntry entry : entryIndex.getMethods()) { 89 entryIndex.getMethods().parallelStream().forEach(entry -> {
83 AccessFlags entryAcc = entryIndex.getMethodAccess(entry); 90 AccessFlags entryAcc = entryIndex.getMethodAccess(entry);
84 91
85 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { 92 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
@@ -89,9 +96,9 @@ public class PackageVisibilityIndex implements JarIndexer {
89 } 96 }
90 } 97 }
91 } 98 }
92 } 99 });
93 100
94 for (ClassEntry entry : entryIndex.getClasses()) { 101 entryIndex.getClasses().parallelStream().forEach(entry -> {
95 AccessFlags entryAcc = entryIndex.getClassAccess(entry); 102 AccessFlags entryAcc = entryIndex.getClassAccess(entry);
96 103
97 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { 104 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
@@ -121,7 +128,7 @@ public class PackageVisibilityIndex implements JarIndexer {
121 if (outerClass != null) { 128 if (outerClass != null) {
122 addConnection(entry, outerClass); 129 addConnection(entry, outerClass);
123 } 130 }
124 } 131 });
125 } 132 }
126 133
127 private void addPartitions(EntryIndex entryIndex) { 134 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 @@
1package cuchaz.enigma.analysis.index; 1package cuchaz.enigma.analysis.index;
2 2
3import java.util.Collection; 3import java.util.Collection;
4import java.util.Map; 4import java.util.Collections;
5 5import java.util.List;
6import com.google.common.collect.HashMultimap; 6import java.util.concurrent.ConcurrentHashMap;
7import com.google.common.collect.Multimap; 7import java.util.concurrent.ConcurrentMap;
8 8
9import cuchaz.enigma.analysis.EntryReference; 9import cuchaz.enigma.analysis.EntryReference;
10import cuchaz.enigma.analysis.ReferenceTargetType; 10import cuchaz.enigma.analysis.ReferenceTargetType;
@@ -20,13 +20,13 @@ import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry; 20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21 21
22public class ReferenceIndex implements JarIndexer { 22public class ReferenceIndex implements JarIndexer {
23 private Multimap<MethodEntry, MethodEntry> methodReferences = HashMultimap.create(); 23 private ConcurrentMap<MethodEntry, List<MethodEntry>> methodReferences = new ConcurrentHashMap<>();
24 24
25 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> referencesToMethods = HashMultimap.create(); 25 private ConcurrentMap<MethodEntry, List<EntryReference<MethodEntry, MethodDefEntry>>> referencesToMethods = new ConcurrentHashMap<>();
26 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> referencesToClasses = HashMultimap.create(); 26 private ConcurrentMap<ClassEntry, List<EntryReference<ClassEntry, MethodDefEntry>>> referencesToClasses = new ConcurrentHashMap<>();
27 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> referencesToFields = HashMultimap.create(); 27 private ConcurrentMap<FieldEntry, List<EntryReference<FieldEntry, MethodDefEntry>>> referencesToFields = new ConcurrentHashMap<>();
28 private Multimap<ClassEntry, EntryReference<ClassEntry, FieldDefEntry>> fieldTypeReferences = HashMultimap.create(); 28 private ConcurrentMap<ClassEntry, List<EntryReference<ClassEntry, FieldDefEntry>>> fieldTypeReferences = new ConcurrentHashMap<>();
29 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> methodTypeReferences = HashMultimap.create(); 29 private ConcurrentMap<ClassEntry, List<EntryReference<ClassEntry, MethodDefEntry>>> methodTypeReferences = new ConcurrentHashMap<>();
30 30
31 @Override 31 @Override
32 public void indexMethod(MethodDefEntry methodEntry) { 32 public void indexMethod(MethodDefEntry methodEntry) {
@@ -44,7 +44,7 @@ public class ReferenceIndex implements JarIndexer {
44 private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { 44 private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) {
45 if (typeDescriptor.isType()) { 45 if (typeDescriptor.isType()) {
46 ClassEntry referencedClass = typeDescriptor.getTypeEntry(); 46 ClassEntry referencedClass = typeDescriptor.getTypeEntry();
47 methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); 47 JarIndex.synchronizedAdd(methodTypeReferences, referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method));
48 } else if (typeDescriptor.isArray()) { 48 } else if (typeDescriptor.isArray()) {
49 indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); 49 indexMethodTypeDescriptor(method, typeDescriptor.getArrayType());
50 } 50 }
@@ -58,7 +58,7 @@ public class ReferenceIndex implements JarIndexer {
58 private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { 58 private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) {
59 if (typeDescriptor.isType()) { 59 if (typeDescriptor.isType()) {
60 ClassEntry referencedClass = typeDescriptor.getTypeEntry(); 60 ClassEntry referencedClass = typeDescriptor.getTypeEntry();
61 fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); 61 JarIndex.synchronizedAdd(fieldTypeReferences, referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field));
62 } else if (typeDescriptor.isArray()) { 62 } else if (typeDescriptor.isArray()) {
63 indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); 63 indexFieldTypeDescriptor(field, typeDescriptor.getArrayType());
64 } 64 }
@@ -66,23 +66,23 @@ public class ReferenceIndex implements JarIndexer {
66 66
67 @Override 67 @Override
68 public void indexClassReference(MethodDefEntry callerEntry, ClassEntry referencedEntry, ReferenceTargetType targetType) { 68 public void indexClassReference(MethodDefEntry callerEntry, ClassEntry referencedEntry, ReferenceTargetType targetType) {
69 referencesToClasses.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); 69 JarIndex.synchronizedAdd(referencesToClasses, referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType));
70 } 70 }
71 71
72 @Override 72 @Override
73 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) { 73 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) {
74 referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); 74 JarIndex.synchronizedAdd(referencesToMethods, referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType));
75 methodReferences.put(callerEntry, referencedEntry); 75 JarIndex.synchronizedAdd(methodReferences, callerEntry, referencedEntry);
76 76
77 if (referencedEntry.isConstructor()) { 77 if (referencedEntry.isConstructor()) {
78 ClassEntry referencedClass = referencedEntry.getParent(); 78 ClassEntry referencedClass = referencedEntry.getParent();
79 referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType)); 79 JarIndex.synchronizedAdd(referencesToClasses, referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType));
80 } 80 }
81 } 81 }
82 82
83 @Override 83 @Override
84 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) { 84 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) {
85 referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType)); 85 JarIndex.synchronizedAdd(referencesToFields, referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType));
86 } 86 }
87 87
88 @Override 88 @Override
@@ -108,24 +108,26 @@ public class ReferenceIndex implements JarIndexer {
108 methodTypeReferences = remapReferencesTo(index, methodTypeReferences); 108 methodTypeReferences = remapReferencesTo(index, methodTypeReferences);
109 } 109 }
110 110
111 private <K extends Entry<?>, V extends Entry<?>> Multimap<K, V> remapReferences(JarIndex index, Multimap<K, V> multimap) { 111 private <K extends Entry<?>, V extends Entry<?>> ConcurrentMap<K, List<V>> remapReferences(JarIndex index, ConcurrentMap<K, List<V>> multimap) {
112 final int keySetSize = multimap.keySet().size(); 112 ConcurrentMap<K, List<V>> resolved = new ConcurrentHashMap<>();
113 Multimap<K, V> resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize);
114 113
115 for (Map.Entry<K, V> entry : multimap.entries()) { 114 multimap.entrySet().parallelStream().forEach(entry -> {
116 resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); 115 for (V value : entry.getValue()) {
117 } 116 JarIndex.synchronizedAdd(resolved, remap(index, entry.getKey()), remap(index, value));
117 }
118 });
118 119
119 return resolved; 120 return resolved;
120 } 121 }
121 122
122 private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) { 123 private <E extends Entry<?>, C extends Entry<?>> ConcurrentMap<E, List<EntryReference<E, C>>> remapReferencesTo(JarIndex index, ConcurrentMap<E, List<EntryReference<E, C>>> multimap) {
123 final int keySetSize = multimap.keySet().size(); 124 ConcurrentMap<E, List<EntryReference<E, C>>> resolved = new ConcurrentHashMap<>();
124 Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize);
125 125
126 for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) { 126 multimap.entrySet().parallelStream().forEach(entry -> {
127 resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); 127 for (EntryReference<E, C> value : entry.getValue()) {
128 } 128 JarIndex.synchronizedAdd(resolved, remap(index, entry.getKey()), remap(index, value));
129 }
130 });
129 131
130 return resolved; 132 return resolved;
131 } 133 }
@@ -139,26 +141,26 @@ public class ReferenceIndex implements JarIndexer {
139 } 141 }
140 142
141 public Collection<MethodEntry> getMethodsReferencedBy(MethodEntry entry) { 143 public Collection<MethodEntry> getMethodsReferencedBy(MethodEntry entry) {
142 return methodReferences.get(entry); 144 return methodReferences.getOrDefault(entry, Collections.emptyList());
143 } 145 }
144 146
145 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getReferencesToField(FieldEntry entry) { 147 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getReferencesToField(FieldEntry entry) {
146 return referencesToFields.get(entry); 148 return referencesToFields.getOrDefault(entry, Collections.emptyList());
147 } 149 }
148 150
149 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getReferencesToClass(ClassEntry entry) { 151 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getReferencesToClass(ClassEntry entry) {
150 return referencesToClasses.get(entry); 152 return referencesToClasses.getOrDefault(entry, Collections.emptyList());
151 } 153 }
152 154
153 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) { 155 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) {
154 return referencesToMethods.get(entry); 156 return referencesToMethods.getOrDefault(entry, Collections.emptyList());
155 } 157 }
156 158
157 public Collection<EntryReference<ClassEntry, FieldDefEntry>> getFieldTypeReferencesToClass(ClassEntry entry) { 159 public Collection<EntryReference<ClassEntry, FieldDefEntry>> getFieldTypeReferencesToClass(ClassEntry entry) {
158 return fieldTypeReferences.get(entry); 160 return fieldTypeReferences.getOrDefault(entry, Collections.emptyList());
159 } 161 }
160 162
161 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodTypeReferencesToClass(ClassEntry entry) { 163 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodTypeReferencesToClass(ClassEntry entry) {
162 return methodTypeReferences.get(entry); 164 return methodTypeReferences.getOrDefault(entry, Collections.emptyList());
163 } 165 }
164} 166}
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 @@
1package cuchaz.enigma.api.service; 1package cuchaz.enigma.api.service;
2 2
3import java.util.Collection;
3import java.util.Set; 4import java.util.Set;
5import java.util.concurrent.CopyOnWriteArrayList;
6import java.util.function.Consumer;
7import java.util.function.Supplier;
4 8
5import org.objectweb.asm.ClassVisitor; 9import org.objectweb.asm.ClassVisitor;
6 10
@@ -19,4 +23,21 @@ public interface JarIndexerService extends EnigmaService {
19 } 23 }
20 }; 24 };
21 } 25 }
26
27 /**
28 * Creates multiple thread-local {@code ClassVisitor}s, runs each on a subset of classes on their own thread, and
29 * combines them at the end.
30 */
31 static <V extends ClassVisitor> JarIndexerService fromVisitorsInParallel(Supplier<V> visitorCreator, Consumer<Collection<V>> combiner) {
32 return (scope, classProvider, jarIndex) -> {
33 CopyOnWriteArrayList<V> allVisitors = new CopyOnWriteArrayList<>();
34 ThreadLocal<V> visitors = ThreadLocal.withInitial(() -> {
35 V visitor = visitorCreator.get();
36 allVisitors.add(visitor);
37 return visitor;
38 });
39 scope.parallelStream().forEach(className -> classProvider.get(className).accept(visitors.get()));
40 combiner.accept(allVisitors);
41 };
42 }
22} 43}
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 @@
1package cuchaz.enigma.classprovider;
2
3import java.util.Collection;
4
5import org.jetbrains.annotations.Nullable;
6import org.objectweb.asm.ClassReader;
7import org.objectweb.asm.ClassWriter;
8import org.objectweb.asm.Opcodes;
9import org.objectweb.asm.tree.ClassNode;
10
11import cuchaz.enigma.analysis.IndexClassWriter;
12import cuchaz.enigma.analysis.index.EntryIndex;
13
14public class AddFramesIfNecessaryClassProvider implements ClassProvider {
15 private final ClassProvider delegate;
16 private final EntryIndex entryIndex;
17
18 public AddFramesIfNecessaryClassProvider(ClassProvider delegate, EntryIndex entryIndex) {
19 this.delegate = delegate;
20 this.entryIndex = entryIndex;
21 }
22
23 @Override
24 public Collection<String> getClassNames() {
25 return delegate.getClassNames();
26 }
27
28 @Override
29 @Nullable
30 public ClassNode get(String name) {
31 ClassNode clazz = delegate.get(name);
32
33 if (clazz == null) {
34 return null;
35 }
36
37 if (clazz.version >= Opcodes.V1_7) {
38 // already has frames
39 return clazz;
40 }
41
42 IndexClassWriter cw = new IndexClassWriter(entryIndex, ClassWriter.COMPUTE_FRAMES);
43 clazz.accept(cw);
44 ClassReader cr = new ClassReader(cw.toByteArray());
45 ClassNode node = new ClassNode();
46 cr.accept(node, 0);
47 return node;
48 }
49}
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;
9 9
10import javax.annotation.Nullable; 10import javax.annotation.Nullable;
11 11
12import org.jetbrains.annotations.ApiStatus;
13import net.fabricmc.mappingio.MappedElementKind; 12import net.fabricmc.mappingio.MappedElementKind;
14import net.fabricmc.mappingio.tree.MemoryMappingTree;
15import net.fabricmc.mappingio.tree.VisitableMappingTree;
16import net.fabricmc.mappingio.tree.MappingTree.ClassMapping; 13import net.fabricmc.mappingio.tree.MappingTree.ClassMapping;
17import net.fabricmc.mappingio.tree.MappingTree.FieldMapping; 14import net.fabricmc.mappingio.tree.MappingTree.FieldMapping;
18import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping; 15import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping;
19import net.fabricmc.mappingio.tree.MappingTree.MethodMapping; 16import net.fabricmc.mappingio.tree.MappingTree.MethodMapping;
20import net.fabricmc.mappingio.tree.MappingTree.MethodVarMapping; 17import net.fabricmc.mappingio.tree.MappingTree.MethodVarMapping;
18import net.fabricmc.mappingio.tree.MemoryMappingTree;
19import net.fabricmc.mappingio.tree.VisitableMappingTree;
20import org.jetbrains.annotations.ApiStatus;
21 21
22import cuchaz.enigma.ProgressListener; 22import cuchaz.enigma.ProgressListener;
23import cuchaz.enigma.analysis.index.JarIndex; 23import 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 @@
11 11
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod;
14import static cuchaz.enigma.TestEntryFactory.newClass; 15import static cuchaz.enigma.TestEntryFactory.newClass;
15import static cuchaz.enigma.TestEntryFactory.newField; 16import static cuchaz.enigma.TestEntryFactory.newField;
16import static cuchaz.enigma.TestEntryFactory.newMethod;
17import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByMethod; 17import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByMethod;
18import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; 18import static cuchaz.enigma.TestEntryFactory.newMethod;
19import static org.hamcrest.MatcherAssert.assertThat; 19import static org.hamcrest.MatcherAssert.assertThat;
20import static org.hamcrest.Matchers.contains; 20import static org.hamcrest.Matchers.contains;
21import static org.hamcrest.Matchers.containsInAnyOrder; 21import static org.hamcrest.Matchers.containsInAnyOrder;