From 00fcd0550fcdda621c2e4662f6ddd55ce673b931 Mon Sep 17 00:00:00 2001 From: Gegy Date: Thu, 24 Jan 2019 14:48:32 +0200 Subject: [WIP] Mapping rework (#91) * Move packages * Mapping & entry refactor: first pass * Fix deobf -> obf tree remapping * Resolve various issues * Give all entries the potential for parents and treat inner classes as children * Deobf UI tree elements * Tests pass * Sort mapping output * Fix delta tracking * Index separation and first pass for #97 * Keep track of remapped jar index * Fix child entries not being remapped * Drop non-root entries * Track dropped mappings * Fix enigma mapping ordering * EntryTreeNode interface * Small tweaks * Naive full index remap on rename * Entries can resolve to more than one root entry * Support alternative resolution strategies * Bridge method resolution * Tests pass * Fix mappings being used where there are none * Fix methods with different descriptors being considered unique. closes #89 --- .../enigma/analysis/index/BridgeMethodIndex.java | 77 ++++++++++ .../cuchaz/enigma/analysis/index/EntryIndex.java | 109 ++++++++++++++ .../enigma/analysis/index/IndexClassVisitor.java | 40 +++++ .../analysis/index/IndexReferenceVisitor.java | 83 +++++++++++ .../enigma/analysis/index/InheritanceIndex.java | 97 ++++++++++++ .../cuchaz/enigma/analysis/index/JarIndex.java | 165 +++++++++++++++++++++ .../cuchaz/enigma/analysis/index/JarIndexer.java | 24 +++ .../enigma/analysis/index/ReferenceIndex.java | 83 +++++++++++ .../enigma/analysis/index/RemappableIndex.java | 9 ++ 9 files changed, 687 insertions(+) create mode 100644 src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/JarIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java create mode 100644 src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java (limited to 'src/main/java/cuchaz/enigma/analysis/index') diff --git a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java new file mode 100644 index 0000000..e1903d9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java @@ -0,0 +1,77 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.Maps; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.MethodEntry; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Map; + +public class BridgeMethodIndex implements JarIndexer, RemappableIndex { + private final EntryIndex entryIndex; + private final ReferenceIndex referenceIndex; + + private Map accessedToBridge = Maps.newHashMap(); + + public BridgeMethodIndex(EntryIndex entryIndex, ReferenceIndex referenceIndex) { + this.entryIndex = entryIndex; + this.referenceIndex = referenceIndex; + } + + @Override + public void remap(Translator translator) { + accessedToBridge = translator.translate(accessedToBridge); + } + + @Override + public BridgeMethodIndex remapped(Translator translator) { + BridgeMethodIndex index = new BridgeMethodIndex(entryIndex, referenceIndex); + index.accessedToBridge = translator.translate(accessedToBridge); + + return index; + } + + @Override + public void processIndex(EntryResolver resolver) { + // look for access and bridged methods + for (MethodEntry methodEntry : entryIndex.getMethods()) { + AccessFlags access = entryIndex.getMethodAccess(methodEntry); + if (access == null || !access.isSynthetic()) { + continue; + } + + indexSyntheticMethod(methodEntry, access); + } + } + + private void indexSyntheticMethod(MethodEntry syntheticMethod, AccessFlags access) { + if (access.isBridge()) { + MethodEntry accessedMethod = findAccessMethod(syntheticMethod); + if (accessedMethod != null) { + accessedToBridge.put(accessedMethod, syntheticMethod); + } + } + } + + private MethodEntry findAccessMethod(MethodEntry method) { + // we want to find all compiler-added methods that directly call another with no processing + + // get all the methods that we call + final Collection referencedMethods = referenceIndex.getMethodsReferencedBy(method); + + // is there just one? + if (referencedMethods.size() != 1) { + return null; + } + + return referencedMethods.stream().findFirst().orElse(null); + } + + @Nullable + public MethodEntry getBridgeFromAccessed(MethodEntry entry) { + return accessedToBridge.get(entry); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java new file mode 100644 index 0000000..55bfbc2 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java @@ -0,0 +1,109 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.*; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class EntryIndex implements JarIndexer, RemappableIndex { + private Map classes = new HashMap<>(); + private Map fields = new HashMap<>(); + private Map methods = new HashMap<>(); + + @Override + public void remap(Translator translator) { + classes = translator.translateKeys(classes); + fields = translator.translateKeys(fields); + methods = translator.translateKeys(methods); + } + + @Override + public EntryIndex remapped(Translator translator) { + EntryIndex index = new EntryIndex(); + index.classes = translator.translateKeys(classes); + index.fields = translator.translateKeys(fields); + index.methods = translator.translateKeys(methods); + + return index; + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + classes.put(classEntry, classEntry.getAccess()); + } + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + methods.put(methodEntry, methodEntry.getAccess()); + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + fields.put(fieldEntry, fieldEntry.getAccess()); + } + + public boolean hasClass(ClassEntry entry) { + return classes.containsKey(entry); + } + + public boolean hasMethod(MethodEntry entry) { + return methods.containsKey(entry); + } + + public boolean hasField(FieldEntry entry) { + return fields.containsKey(entry); + } + + public boolean hasEntry(Entry entry) { + if (entry instanceof ClassEntry) { + return hasClass((ClassEntry) entry); + } else if (entry instanceof MethodEntry) { + return hasMethod((MethodEntry) entry); + } else if (entry instanceof FieldEntry) { + return hasField((FieldEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return hasMethod(((LocalVariableEntry) entry).getParent()); + } + + return false; + } + + @Nullable + public AccessFlags getMethodAccess(MethodEntry entry) { + return methods.get(entry); + } + + @Nullable + public AccessFlags getFieldAccess(FieldEntry entry) { + return fields.get(entry); + } + + @Nullable + public AccessFlags getEntryAccess(Entry entry) { + if (entry instanceof MethodEntry) { + return getMethodAccess((MethodEntry) entry); + } else if (entry instanceof FieldEntry) { + return getFieldAccess((FieldEntry) entry); + } else if (entry instanceof LocalVariableEntry) { + return getMethodAccess(((LocalVariableEntry) entry).getParent()); + } + + return null; + } + + public Collection getClasses() { + return classes.keySet(); + } + + public Collection getMethods() { + return methods.keySet(); + } + + public Collection getFields() { + return fields.keySet(); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java new file mode 100644 index 0000000..f9cb23c --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java @@ -0,0 +1,40 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +public class IndexClassVisitor extends ClassVisitor { + private final JarIndexer indexer; + private ClassDefEntry classEntry; + + public IndexClassVisitor(JarIndex indexer, int api) { + super(api); + this.indexer = indexer; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces); + indexer.indexClass(classEntry); + + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature)); + + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature)); + + return super.visitMethod(access, name, desc, signature, exceptions); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java new file mode 100644 index 0000000..ba5d3b6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java @@ -0,0 +1,83 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Handle; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class IndexReferenceVisitor extends ClassVisitor { + private final JarIndexer indexer; + private ClassEntry classEntry; + + public IndexReferenceVisitor(JarIndexer indexer, int api) { + super(api); + this.indexer = indexer; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.classEntry = new ClassEntry(name); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access)); + return new Method(this.indexer, entry, this.api); + } + + private static class Method extends MethodVisitor { + private final JarIndexer indexer; + private final MethodDefEntry callerEntry; + + public Method(JarIndexer indexer, MethodDefEntry callerEntry, int api) { + super(api); + this.indexer = indexer; + this.callerEntry = callerEntry; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + FieldEntry fieldEntry = FieldEntry.parse(owner, name, desc); + this.indexer.indexFieldReference(callerEntry, fieldEntry); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + MethodEntry methodEntry = MethodEntry.parse(owner, name, desc); + this.indexer.indexMethodReference(callerEntry, methodEntry); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + for (Object bsmArg : bsmArgs) { + if (bsmArg instanceof Handle) { + Handle handle = (Handle) bsmArg; + switch (handle.getTag()) { + case Opcodes.H_GETFIELD: + case Opcodes.H_GETSTATIC: + case Opcodes.H_PUTFIELD: + case Opcodes.H_PUTSTATIC: + FieldEntry fieldEntry = FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + this.indexer.indexFieldReference(callerEntry, fieldEntry); + break; + case Opcodes.H_INVOKEINTERFACE: + case Opcodes.H_INVOKESPECIAL: + case Opcodes.H_INVOKESTATIC: + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_NEWINVOKESPECIAL: + MethodEntry methodEntry = MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + this.indexer.indexMethodReference(callerEntry, methodEntry); + break; + } + } + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java new file mode 100644 index 0000000..d165cc8 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.Set; + +public class InheritanceIndex implements JarIndexer, RemappableIndex { + private Multimap classParents = HashMultimap.create(); + private Multimap classChildren = HashMultimap.create(); + + @Override + public void remap(Translator translator) { + classChildren = translator.translate(classChildren); + classParents = translator.translate(classParents); + } + + @Override + public InheritanceIndex remapped(Translator translator) { + InheritanceIndex index = new InheritanceIndex(); + index.classParents = translator.translate(classParents); + index.classChildren = translator.translate(classChildren); + + return index; + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + ClassEntry superClass = classEntry.getSuperClass(); + if (superClass != null) { + indexParent(classEntry, superClass); + } + + for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { + indexParent(classEntry, interfaceEntry); + } + } + + private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) { + if (childEntry.isJre() || parentEntry.isJre()) { + return; + } + classParents.put(childEntry, parentEntry); + classChildren.put(parentEntry, childEntry); + } + + public Collection getParents(ClassEntry classEntry) { + return classParents.get(classEntry); + } + + public Collection getChildren(ClassEntry classEntry) { + return classChildren.get(classEntry); + } + + public Set getAncestors(ClassEntry classEntry) { + Set ancestors = Sets.newHashSet(); + + LinkedList ancestorQueue = new LinkedList<>(); + ancestorQueue.push(classEntry); + + while (!ancestorQueue.isEmpty()) { + ClassEntry ancestor = ancestorQueue.pop(); + Collection parents = getParents(ancestor); + + parents.forEach(ancestorQueue::push); + ancestors.addAll(parents); + } + + return ancestors; + } + + public boolean isParent(ClassEntry classEntry) { + return classChildren.containsKey(classEntry); + } + + public boolean hasParents(ClassEntry classEntry) { + Collection parents = classParents.get(classEntry); + return parents != null && !parents.isEmpty(); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java new file mode 100644 index 0000000..0880244 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java @@ -0,0 +1,165 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.IndexEntryResolver; +import cuchaz.enigma.translation.representation.entry.*; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Consumer; + +public class JarIndex implements JarIndexer, RemappableIndex { + private final EntryIndex entryIndex; + private final InheritanceIndex inheritanceIndex; + private final ReferenceIndex referenceIndex; + private final BridgeMethodIndex bridgeMethodIndex; + private final EntryResolver entryResolver; + + private final Collection indexers; + + private final Multimap methodImplementations = HashMultimap.create(); + + public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex) { + this.entryIndex = entryIndex; + this.inheritanceIndex = inheritanceIndex; + this.referenceIndex = referenceIndex; + this.bridgeMethodIndex = bridgeMethodIndex; + this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); + this.entryResolver = new IndexEntryResolver(this); + } + + public static JarIndex empty() { + EntryIndex entryIndex = new EntryIndex(); + InheritanceIndex inheritanceIndex = new InheritanceIndex(); + ReferenceIndex referenceIndex = new ReferenceIndex(); + BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, referenceIndex); + return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); + } + + @Override + public void remap(Translator translator) { + entryIndex.remap(translator); + inheritanceIndex.remap(translator); + bridgeMethodIndex.remap(translator); + } + + @Override + public JarIndex remapped(Translator translator) { + EntryIndex entryIndex = this.entryIndex.remapped(translator); + InheritanceIndex inheritanceIndex = this.inheritanceIndex.remapped(translator); + BridgeMethodIndex bridgeMethodIndex = this.bridgeMethodIndex.remapped(translator); + + JarIndex remappedIndex = new JarIndex(entryIndex, inheritanceIndex, this.referenceIndex, bridgeMethodIndex); + remappedIndex.methodImplementations.putAll(methodImplementations); + + return remappedIndex; + } + + public void indexJar(ParsedJar jar, Consumer progress) { + progress.accept("Indexing entries (1/3)"); + jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); + + progress.accept("Indexing entry references (2/3)"); + jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); + + progress.accept("Processing index (3/3)"); + processIndex(entryResolver); + } + + @Override + public void processIndex(EntryResolver resolver) { + indexers.forEach(indexer -> indexer.processIndex(entryResolver)); + } + + @Override + public void indexClass(ClassDefEntry classEntry) { + if (classEntry.isJre()) { + return; + } + + for (ClassEntry interfaceEntry : classEntry.getInterfaces()) { + if (classEntry.equals(interfaceEntry)) { + throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry); + } + } + + indexers.forEach(indexer -> indexer.indexClass(classEntry)); + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + if (fieldEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexField(fieldEntry)); + } + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + if (methodEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexMethod(methodEntry)); + + if (!methodEntry.isConstructor()) { + methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry); + } + } + + @Override + public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry)); + } + + @Override + public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry)); + } + + public EntryIndex getEntryIndex() { + return entryIndex; + } + + public InheritanceIndex getInheritanceIndex() { + return this.inheritanceIndex; + } + + public ReferenceIndex getReferenceIndex() { + return referenceIndex; + } + + public BridgeMethodIndex getBridgeMethodIndex() { + return bridgeMethodIndex; + } + + public EntryResolver getEntryResolver() { + return entryResolver; + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java new file mode 100644 index 0000000..a087e59 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java @@ -0,0 +1,24 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.representation.entry.*; + +public interface JarIndexer { + default void indexClass(ClassDefEntry classEntry) { + } + + default void indexField(FieldDefEntry fieldEntry) { + } + + default void indexMethod(MethodDefEntry methodEntry) { + } + + default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { + } + + default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { + } + + default void processIndex(EntryResolver resolver) { + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java new file mode 100644 index 0000000..ac11da4 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java @@ -0,0 +1,83 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.Collection; +import java.util.Map; + +public class ReferenceIndex implements JarIndexer { + private Multimap methodReferences = HashMultimap.create(); + + private Multimap> referencesToMethods = HashMultimap.create(); + private Multimap> referencesToClasses = HashMultimap.create(); + private Multimap> referencesToFields = HashMultimap.create(); + + @Override + public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { + referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry)); + methodReferences.put(callerEntry, referencedEntry); + + if (referencedEntry.isConstructor()) { + ClassEntry referencedClass = referencedEntry.getParent(); + referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry)); + } + } + + @Override + public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { + referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry)); + } + + @Override + public void processIndex(EntryResolver resolver) { + methodReferences = resolveReferences(resolver, methodReferences); + referencesToMethods = resolveReferencesTo(resolver, referencesToMethods); + referencesToClasses = resolveReferencesTo(resolver, referencesToClasses); + referencesToFields = resolveReferencesTo(resolver, referencesToFields); + } + + private , V extends Entry> Multimap resolveReferences(EntryResolver resolver, Multimap multimap) { + Multimap resolved = HashMultimap.create(); + for (Map.Entry entry : multimap.entries()) { + resolved.put(resolve(resolver, entry.getKey()), resolve(resolver, entry.getValue())); + } + return resolved; + } + + private , C extends Entry> Multimap> resolveReferencesTo(EntryResolver resolver, Multimap> multimap) { + Multimap> resolved = HashMultimap.create(); + for (Map.Entry> entry : multimap.entries()) { + resolved.put(resolve(resolver, entry.getKey()), resolve(resolver, entry.getValue())); + } + return resolved; + } + + private > E resolve(EntryResolver resolver, E entry) { + return resolver.resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST); + } + + private , C extends Entry> EntryReference resolve(EntryResolver resolver, EntryReference reference) { + return resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); + } + + public Collection getMethodsReferencedBy(MethodEntry entry) { + return methodReferences.get(entry); + } + + public Collection> getReferencesToField(FieldEntry entry) { + return referencesToFields.get(entry); + } + + public Collection> getReferencesToClass(ClassEntry entry) { + return referencesToClasses.get(entry); + } + + public Collection> getReferencesToMethod(MethodEntry entry) { + return referencesToMethods.get(entry); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java b/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java new file mode 100644 index 0000000..537e772 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/RemappableIndex.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.analysis.index; + +import cuchaz.enigma.translation.Translator; + +public interface RemappableIndex { + void remap(Translator translator); + + RemappableIndex remapped(Translator translator); +} -- cgit v1.2.3