diff options
22 files changed, 517 insertions, 507 deletions
diff --git a/build.gradle b/build.gradle index 46794c5..4453ab9 100644 --- a/build.gradle +++ b/build.gradle | |||
| @@ -1,12 +1,14 @@ | |||
| 1 | plugins { | 1 | plugins { |
| 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 | ||
| 6 | subprojects { | 7 | subprojects { |
| 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 3beae21..19b8540 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 106859c..da1a247 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; | |||
| 5 | import java.awt.Desktop; | 5 | import java.awt.Desktop; |
| 6 | import java.awt.Font; | 6 | import java.awt.Font; |
| 7 | import java.awt.Toolkit; | 7 | import java.awt.Toolkit; |
| 8 | import java.awt.datatransfer.DataFlavor; | ||
| 8 | import java.awt.datatransfer.StringSelection; | 9 | import java.awt.datatransfer.StringSelection; |
| 10 | import java.awt.datatransfer.UnsupportedFlavorException; | ||
| 9 | import java.awt.event.MouseAdapter; | 11 | import java.awt.event.MouseAdapter; |
| 10 | import java.awt.event.MouseEvent; | 12 | import java.awt.event.MouseEvent; |
| 11 | import java.awt.event.MouseListener; | 13 | import java.awt.event.MouseListener; |
| 12 | import java.awt.event.WindowAdapter; | 14 | import java.awt.event.WindowAdapter; |
| 13 | import java.awt.event.WindowEvent; | 15 | import java.awt.event.WindowEvent; |
| 14 | import java.awt.event.WindowListener; | 16 | import java.awt.event.WindowListener; |
| 15 | import java.awt.datatransfer.DataFlavor; | ||
| 16 | import java.awt.datatransfer.UnsupportedFlavorException; | ||
| 17 | import java.awt.font.TextAttribute; | 17 | import java.awt.font.TextAttribute; |
| 18 | import java.io.IOException; | 18 | import java.io.IOException; |
| 19 | import java.net.URI; | 19 | 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 696a848..414285d 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 0000000..976b2e6 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/BetterAnalyzerAdapter.java | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import java.util.ArrayList; | ||
| 4 | import java.util.Collections; | ||
| 5 | import java.util.List; | ||
| 6 | |||
| 7 | import org.jetbrains.annotations.Nullable; | ||
| 8 | import org.objectweb.asm.ClassReader; | ||
| 9 | import org.objectweb.asm.MethodVisitor; | ||
| 10 | import org.objectweb.asm.Opcodes; | ||
| 11 | import 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 | */ | ||
| 16 | public 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 10bc436..0f53d26 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 0000000..517efea --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/analysis/IndexClassWriter.java | |||
| @@ -0,0 +1,133 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import org.jetbrains.annotations.Nullable; | ||
| 4 | import org.objectweb.asm.ClassWriter; | ||
| 5 | import org.objectweb.asm.Type; | ||
| 6 | |||
| 7 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 8 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 10 | |||
| 11 | public 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 44a768e..0000000 --- a/enigma/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java +++ /dev/null | |||
| @@ -1,160 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import java.util.Set; | ||
| 4 | |||
| 5 | import org.objectweb.asm.Type; | ||
| 6 | import org.objectweb.asm.tree.analysis.BasicValue; | ||
| 7 | import org.objectweb.asm.tree.analysis.SimpleVerifier; | ||
| 8 | |||
| 9 | import cuchaz.enigma.Enigma; | ||
| 10 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 11 | import cuchaz.enigma.analysis.index.InheritanceIndex; | ||
| 12 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 15 | |||
| 16 | public 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 2ca1dfd..0000000 --- a/enigma/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java +++ /dev/null | |||
| @@ -1,106 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import java.util.List; | ||
| 4 | import java.util.Objects; | ||
| 5 | |||
| 6 | import org.objectweb.asm.Type; | ||
| 7 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 8 | import org.objectweb.asm.tree.analysis.AnalyzerException; | ||
| 9 | import org.objectweb.asm.tree.analysis.Interpreter; | ||
| 10 | import org.objectweb.asm.tree.analysis.Value; | ||
| 11 | |||
| 12 | import cuchaz.enigma.Enigma; | ||
| 13 | |||
| 14 | public 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 8dc7fe6..0000000 --- a/enigma/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java +++ /dev/null | |||
| @@ -1,19 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import java.util.function.Consumer; | ||
| 4 | |||
| 5 | import org.objectweb.asm.tree.MethodNode; | ||
| 6 | |||
| 7 | public 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 b3ba896..95c2c2a 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 26093c3..4a98c56 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; | |||
| 5 | import java.util.HashMap; | 5 | import java.util.HashMap; |
| 6 | import java.util.List; | 6 | import java.util.List; |
| 7 | import java.util.Map; | 7 | import java.util.Map; |
| 8 | import java.util.concurrent.ConcurrentHashMap; | ||
| 9 | import java.util.concurrent.ConcurrentMap; | ||
| 8 | 10 | ||
| 9 | import javax.annotation.Nullable; | 11 | import javax.annotation.Nullable; |
| 10 | 12 | ||
| 11 | import com.google.common.collect.Maps; | ||
| 12 | |||
| 13 | import cuchaz.enigma.translation.representation.AccessFlags; | 13 | import cuchaz.enigma.translation.representation.AccessFlags; |
| 14 | import cuchaz.enigma.translation.representation.MethodDescriptor; | 14 | import cuchaz.enigma.translation.representation.MethodDescriptor; |
| 15 | import cuchaz.enigma.translation.representation.TypeDescriptor; | 15 | import 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 0e4cdcf..9e65fc3 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 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | 1 | package cuchaz.enigma.analysis.index; |
| 2 | 2 | ||
| 3 | import java.util.Collection; | 3 | import java.util.Collection; |
| 4 | import java.util.HashMap; | 4 | import java.util.concurrent.ConcurrentHashMap; |
| 5 | import java.util.Map; | 5 | import java.util.concurrent.ConcurrentMap; |
| 6 | 6 | ||
| 7 | import javax.annotation.Nullable; | 7 | import javax.annotation.Nullable; |
| 8 | 8 | ||
| @@ -17,10 +17,10 @@ import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | |||
| 17 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | 17 | import cuchaz.enigma.translation.representation.entry.MethodEntry; |
| 18 | 18 | ||
| 19 | public class EntryIndex implements JarIndexer { | 19 | public 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 cb71275..ecd460d 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 | ||
| 5 | import org.objectweb.asm.ClassVisitor; | 5 | import org.objectweb.asm.ClassVisitor; |
| 6 | import org.objectweb.asm.Handle; | 6 | import org.objectweb.asm.Handle; |
| 7 | import org.objectweb.asm.Label; | ||
| 7 | import org.objectweb.asm.MethodVisitor; | 8 | import org.objectweb.asm.MethodVisitor; |
| 8 | import org.objectweb.asm.Opcodes; | 9 | import org.objectweb.asm.Opcodes; |
| 9 | import org.objectweb.asm.Type; | 10 | import org.objectweb.asm.Type; |
| 10 | import org.objectweb.asm.tree.AbstractInsnNode; | 11 | |
| 11 | import org.objectweb.asm.tree.FieldInsnNode; | 12 | import cuchaz.enigma.analysis.BetterAnalyzerAdapter; |
| 12 | import org.objectweb.asm.tree.InvokeDynamicInsnNode; | ||
| 13 | import org.objectweb.asm.tree.LdcInsnNode; | ||
| 14 | import org.objectweb.asm.tree.MethodInsnNode; | ||
| 15 | import org.objectweb.asm.tree.TypeInsnNode; | ||
| 16 | import org.objectweb.asm.tree.analysis.Analyzer; | ||
| 17 | import org.objectweb.asm.tree.analysis.AnalyzerException; | ||
| 18 | import org.objectweb.asm.tree.analysis.BasicValue; | ||
| 19 | import org.objectweb.asm.tree.analysis.SourceInterpreter; | ||
| 20 | import org.objectweb.asm.tree.analysis.SourceValue; | ||
| 21 | |||
| 22 | import cuchaz.enigma.analysis.IndexSimpleVerifier; | ||
| 23 | import cuchaz.enigma.analysis.InterpreterPair; | ||
| 24 | import cuchaz.enigma.analysis.MethodNodeWithAction; | ||
| 25 | import cuchaz.enigma.analysis.ReferenceTargetType; | 13 | import cuchaz.enigma.analysis.ReferenceTargetType; |
| 26 | import cuchaz.enigma.translation.representation.AccessFlags; | 14 | import cuchaz.enigma.translation.representation.AccessFlags; |
| 27 | import cuchaz.enigma.translation.representation.Lambda; | 15 | import cuchaz.enigma.translation.representation.Lambda; |
| @@ -35,16 +23,12 @@ import cuchaz.enigma.translation.representation.entry.ParentedEntry; | |||
| 35 | 23 | ||
| 36 | public class IndexReferenceVisitor extends ClassVisitor { | 24 | public 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 1c60db9..9841aa6 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 | ||
| 12 | package cuchaz.enigma.analysis.index; | 12 | package cuchaz.enigma.analysis.index; |
| 13 | 13 | ||
| 14 | import java.util.ArrayList; | ||
| 14 | import java.util.Collection; | 15 | import java.util.Collection; |
| 16 | import java.util.Collections; | ||
| 15 | import java.util.HashSet; | 17 | import java.util.HashSet; |
| 16 | import java.util.LinkedList; | 18 | import java.util.LinkedList; |
| 19 | import java.util.List; | ||
| 17 | import java.util.Set; | 20 | import java.util.Set; |
| 21 | import java.util.concurrent.ConcurrentHashMap; | ||
| 22 | import java.util.concurrent.ConcurrentMap; | ||
| 18 | 23 | ||
| 19 | import com.google.common.collect.HashMultimap; | ||
| 20 | import com.google.common.collect.Multimap; | ||
| 21 | import com.google.common.collect.Sets; | 24 | import com.google.common.collect.Sets; |
| 22 | 25 | ||
| 23 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | 26 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; |
| @@ -26,8 +29,8 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry; | |||
| 26 | public class InheritanceIndex implements JarIndexer { | 29 | public 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 967983a..cfa177e 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 | ||
| 12 | package cuchaz.enigma.analysis.index; | 12 | package cuchaz.enigma.analysis.index; |
| 13 | 13 | ||
| 14 | import java.util.ArrayList; | ||
| 14 | import java.util.Collection; | 15 | import java.util.Collection; |
| 15 | import java.util.HashSet; | 16 | import java.util.HashSet; |
| 16 | import java.util.List; | 17 | import java.util.List; |
| 18 | import java.util.Map; | ||
| 17 | import java.util.Set; | 19 | import java.util.Set; |
| 18 | 20 | import java.util.concurrent.ConcurrentHashMap; | |
| 19 | import com.google.common.collect.ArrayListMultimap; | 21 | import java.util.concurrent.ConcurrentMap; |
| 20 | import com.google.common.collect.HashMultimap; | ||
| 21 | import com.google.common.collect.ListMultimap; | ||
| 22 | import com.google.common.collect.Multimap; | ||
| 23 | 22 | ||
| 24 | import cuchaz.enigma.Enigma; | 23 | import cuchaz.enigma.Enigma; |
| 25 | import cuchaz.enigma.ProgressListener; | 24 | import cuchaz.enigma.ProgressListener; |
| 26 | import cuchaz.enigma.analysis.ReferenceTargetType; | 25 | import cuchaz.enigma.analysis.ReferenceTargetType; |
| 26 | import cuchaz.enigma.classprovider.AddFramesIfNecessaryClassProvider; | ||
| 27 | import cuchaz.enigma.classprovider.CachingClassProvider; | ||
| 27 | import cuchaz.enigma.classprovider.ClassProvider; | 28 | import cuchaz.enigma.classprovider.ClassProvider; |
| 28 | import cuchaz.enigma.translation.mapping.EntryResolver; | 29 | import cuchaz.enigma.translation.mapping.EntryResolver; |
| 29 | import cuchaz.enigma.translation.mapping.IndexEntryResolver; | 30 | import 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 b400a66..a2ba029 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; | |||
| 6 | import java.util.List; | 6 | import java.util.List; |
| 7 | import java.util.Map; | 7 | import java.util.Map; |
| 8 | import java.util.Set; | 8 | import java.util.Set; |
| 9 | import java.util.concurrent.ConcurrentHashMap; | ||
| 10 | import java.util.concurrent.ConcurrentMap; | ||
| 9 | 11 | ||
| 10 | import com.google.common.collect.HashMultimap; | ||
| 11 | import com.google.common.collect.Lists; | 12 | import com.google.common.collect.Lists; |
| 12 | import com.google.common.collect.Maps; | 13 | import com.google.common.collect.Maps; |
| 13 | import com.google.common.collect.Sets; | 14 | import 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 c55d36a..1a406cb 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 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | 1 | package cuchaz.enigma.analysis.index; |
| 2 | 2 | ||
| 3 | import java.util.Collection; | 3 | import java.util.Collection; |
| 4 | import java.util.Map; | 4 | import java.util.Collections; |
| 5 | 5 | import java.util.List; | |
| 6 | import com.google.common.collect.HashMultimap; | 6 | import java.util.concurrent.ConcurrentHashMap; |
| 7 | import com.google.common.collect.Multimap; | 7 | import java.util.concurrent.ConcurrentMap; |
| 8 | 8 | ||
| 9 | import cuchaz.enigma.analysis.EntryReference; | 9 | import cuchaz.enigma.analysis.EntryReference; |
| 10 | import cuchaz.enigma.analysis.ReferenceTargetType; | 10 | import cuchaz.enigma.analysis.ReferenceTargetType; |
| @@ -20,13 +20,13 @@ import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | |||
| 20 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | 20 | import cuchaz.enigma.translation.representation.entry.MethodEntry; |
| 21 | 21 | ||
| 22 | public class ReferenceIndex implements JarIndexer { | 22 | public 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 3ed6d33..91c79c6 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 @@ | |||
| 1 | package cuchaz.enigma.api.service; | 1 | package cuchaz.enigma.api.service; |
| 2 | 2 | ||
| 3 | import java.util.Collection; | ||
| 3 | import java.util.Set; | 4 | import java.util.Set; |
| 5 | import java.util.concurrent.CopyOnWriteArrayList; | ||
| 6 | import java.util.function.Consumer; | ||
| 7 | import java.util.function.Supplier; | ||
| 4 | 8 | ||
| 5 | import org.objectweb.asm.ClassVisitor; | 9 | import 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 0000000..532631f --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/AddFramesIfNecessaryClassProvider.java | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | package cuchaz.enigma.classprovider; | ||
| 2 | |||
| 3 | import java.util.Collection; | ||
| 4 | |||
| 5 | import org.jetbrains.annotations.Nullable; | ||
| 6 | import org.objectweb.asm.ClassReader; | ||
| 7 | import org.objectweb.asm.ClassWriter; | ||
| 8 | import org.objectweb.asm.Opcodes; | ||
| 9 | import org.objectweb.asm.tree.ClassNode; | ||
| 10 | |||
| 11 | import cuchaz.enigma.analysis.IndexClassWriter; | ||
| 12 | import cuchaz.enigma.analysis.index.EntryIndex; | ||
| 13 | |||
| 14 | public 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 b7696d2..d4e79e1 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 | ||
| 10 | import javax.annotation.Nullable; | 10 | import javax.annotation.Nullable; |
| 11 | 11 | ||
| 12 | import org.jetbrains.annotations.ApiStatus; | ||
| 13 | import net.fabricmc.mappingio.MappedElementKind; | 12 | import net.fabricmc.mappingio.MappedElementKind; |
| 14 | import net.fabricmc.mappingio.tree.MemoryMappingTree; | ||
| 15 | import net.fabricmc.mappingio.tree.VisitableMappingTree; | ||
| 16 | import net.fabricmc.mappingio.tree.MappingTree.ClassMapping; | 13 | import net.fabricmc.mappingio.tree.MappingTree.ClassMapping; |
| 17 | import net.fabricmc.mappingio.tree.MappingTree.FieldMapping; | 14 | import net.fabricmc.mappingio.tree.MappingTree.FieldMapping; |
| 18 | import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping; | 15 | import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping; |
| 19 | import net.fabricmc.mappingio.tree.MappingTree.MethodMapping; | 16 | import net.fabricmc.mappingio.tree.MappingTree.MethodMapping; |
| 20 | import net.fabricmc.mappingio.tree.MappingTree.MethodVarMapping; | 17 | import net.fabricmc.mappingio.tree.MappingTree.MethodVarMapping; |
| 18 | import net.fabricmc.mappingio.tree.MemoryMappingTree; | ||
| 19 | import net.fabricmc.mappingio.tree.VisitableMappingTree; | ||
| 20 | import org.jetbrains.annotations.ApiStatus; | ||
| 21 | 21 | ||
| 22 | import cuchaz.enigma.ProgressListener; | 22 | import cuchaz.enigma.ProgressListener; |
| 23 | import cuchaz.enigma.analysis.index.JarIndex; | 23 | 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 3f6f151..8bd5859 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 | ||
| 12 | package cuchaz.enigma; | 12 | package cuchaz.enigma; |
| 13 | 13 | ||
| 14 | import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; | ||
| 14 | import static cuchaz.enigma.TestEntryFactory.newClass; | 15 | import static cuchaz.enigma.TestEntryFactory.newClass; |
| 15 | import static cuchaz.enigma.TestEntryFactory.newField; | 16 | import static cuchaz.enigma.TestEntryFactory.newField; |
| 16 | import static cuchaz.enigma.TestEntryFactory.newMethod; | ||
| 17 | import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByMethod; | 17 | import static cuchaz.enigma.TestEntryFactory.newFieldReferenceByMethod; |
| 18 | import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; | 18 | import static cuchaz.enigma.TestEntryFactory.newMethod; |
| 19 | import static org.hamcrest.MatcherAssert.assertThat; | 19 | import static org.hamcrest.MatcherAssert.assertThat; |
| 20 | import static org.hamcrest.Matchers.contains; | 20 | import static org.hamcrest.Matchers.contains; |
| 21 | import static org.hamcrest.Matchers.containsInAnyOrder; | 21 | import static org.hamcrest.Matchers.containsInAnyOrder; |