diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/analysis')
7 files changed, 111 insertions, 31 deletions
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java index 101729d..df36c23 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java | |||
| @@ -70,7 +70,7 @@ public class EntryReference<E extends Entry, C extends Entry> { | |||
| 70 | return entry; | 70 | return entry; |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | public String getNamableName() { | 73 | public String getNameableName() { |
| 74 | if (getNameableEntry() instanceof ClassEntry) { | 74 | if (getNameableEntry() instanceof ClassEntry) { |
| 75 | ClassEntry classEntry = (ClassEntry) getNameableEntry(); | 75 | ClassEntry classEntry = (ClassEntry) getNameableEntry(); |
| 76 | if (classEntry.isInnerClass()) { | 76 | if (classEntry.isInnerClass()) { |
diff --git a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java index 69fe54f..4d5e803 100644 --- a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java | |||
| @@ -14,6 +14,11 @@ public class IndexClassVisitor extends ClassVisitor { | |||
| 14 | this.index = index; | 14 | this.index = index; |
| 15 | } | 15 | } |
| 16 | 16 | ||
| 17 | public IndexClassVisitor(JarIndex index, int api, ClassVisitor cv) { | ||
| 18 | super(api, cv); | ||
| 19 | this.index = index; | ||
| 20 | } | ||
| 21 | |||
| 17 | @Override | 22 | @Override |
| 18 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | 23 | public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { |
| 19 | this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces); | 24 | this.classEntry = this.index.indexClass(access, name, signature, superName, interfaces); |
diff --git a/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java index 0474227..b6ab2d5 100644 --- a/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java | |||
| @@ -11,6 +11,11 @@ public class IndexInnerClassVisitor extends ClassVisitor { | |||
| 11 | this.index = index; | 11 | this.index = index; |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | public IndexInnerClassVisitor(JarIndex index, int api, ClassVisitor cv) { | ||
| 15 | super(api, cv); | ||
| 16 | this.index = index; | ||
| 17 | } | ||
| 18 | |||
| 14 | @Override | 19 | @Override |
| 15 | public void visitInnerClass(String name, String outerName, String innerName, int access) { | 20 | public void visitInnerClass(String name, String outerName, String innerName, int access) { |
| 16 | ClassEntry entry = new ClassEntry(name); | 21 | ClassEntry entry = new ClassEntry(name); |
| @@ -19,5 +24,6 @@ public class IndexInnerClassVisitor extends ClassVisitor { | |||
| 19 | ClassEntry outerEntry = new ClassEntry(outerName); | 24 | ClassEntry outerEntry = new ClassEntry(outerName); |
| 20 | index.indexInnerClass(entry, outerEntry); | 25 | index.indexInnerClass(entry, outerEntry); |
| 21 | } | 26 | } |
| 27 | super.visitInnerClass(name, outerName, innerName, access); | ||
| 22 | } | 28 | } |
| 23 | } | 29 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java index 158df4b..e8bda8e 100644 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java | |||
| @@ -15,6 +15,8 @@ import com.google.common.collect.*; | |||
| 15 | import cuchaz.enigma.bytecode.AccessFlags; | 15 | import cuchaz.enigma.bytecode.AccessFlags; |
| 16 | import cuchaz.enigma.mapping.*; | 16 | import cuchaz.enigma.mapping.*; |
| 17 | import cuchaz.enigma.mapping.entry.*; | 17 | import cuchaz.enigma.mapping.entry.*; |
| 18 | import org.objectweb.asm.ClassReader; | ||
| 19 | import org.objectweb.asm.ClassVisitor; | ||
| 18 | import org.objectweb.asm.Opcodes; | 20 | import org.objectweb.asm.Opcodes; |
| 19 | 21 | ||
| 20 | import java.util.*; | 22 | import java.util.*; |
| @@ -62,10 +64,15 @@ public class JarIndex { | |||
| 62 | obfClassEntries.addAll(jar.getClassEntries()); | 64 | obfClassEntries.addAll(jar.getClassEntries()); |
| 63 | 65 | ||
| 64 | // step 2: index classes, fields, methods, interfaces | 66 | // step 2: index classes, fields, methods, interfaces |
| 65 | jar.visit(node -> node.accept(new IndexClassVisitor(this, Opcodes.ASM5))); | 67 | if (buildInnerClasses) { |
| 68 | // + step 5: index inner classes | ||
| 69 | jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5, new IndexInnerClassVisitor(this, Opcodes.ASM5)), ClassReader.SKIP_CODE); | ||
| 70 | } else { | ||
| 71 | jar.visitReader(name -> new IndexClassVisitor(this, Opcodes.ASM5), ClassReader.SKIP_CODE); | ||
| 72 | } | ||
| 66 | 73 | ||
| 67 | // step 3: index field, method, constructor references | 74 | // step 3: index field, method, constructor references |
| 68 | jar.visit(node -> node.accept(new IndexReferenceVisitor(this, Opcodes.ASM5))); | 75 | jar.visitReader(name -> new IndexReferenceVisitor(this, Opcodes.ASM5), ClassReader.SKIP_FRAMES); |
| 69 | 76 | ||
| 70 | // step 4: index access and bridged methods | 77 | // step 4: index access and bridged methods |
| 71 | for (MethodDefEntry methodEntry : methods.values()) { | 78 | for (MethodDefEntry methodEntry : methods.values()) { |
| @@ -79,9 +86,6 @@ public class JarIndex { | |||
| 79 | } | 86 | } |
| 80 | 87 | ||
| 81 | if (buildInnerClasses) { | 88 | if (buildInnerClasses) { |
| 82 | // step 5: index inner classes and anonymous classes | ||
| 83 | jar.visit(node -> node.accept(new IndexInnerClassVisitor(this, Opcodes.ASM5))); | ||
| 84 | |||
| 85 | // step 6: update other indices with inner class info | 89 | // step 6: update other indices with inner class info |
| 86 | Map<String, String> renames = Maps.newHashMap(); | 90 | Map<String, String> renames = Maps.newHashMap(); |
| 87 | for (ClassEntry innerClassEntry : this.innerClassesByOuter.values()) { | 91 | for (ClassEntry innerClassEntry : this.innerClassesByOuter.values()) { |
diff --git a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java index 55f2141..86655d0 100644 --- a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java +++ b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java | |||
| @@ -11,8 +11,10 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma.analysis; | 12 | package cuchaz.enigma.analysis; |
| 13 | 13 | ||
| 14 | import com.google.common.io.ByteStreams; | ||
| 14 | import cuchaz.enigma.mapping.entry.ClassEntry; | 15 | import cuchaz.enigma.mapping.entry.ClassEntry; |
| 15 | import org.objectweb.asm.ClassReader; | 16 | import org.objectweb.asm.ClassReader; |
| 17 | import org.objectweb.asm.ClassVisitor; | ||
| 16 | import org.objectweb.asm.tree.ClassNode; | 18 | import org.objectweb.asm.tree.ClassNode; |
| 17 | 19 | ||
| 18 | import java.io.BufferedInputStream; | 20 | import java.io.BufferedInputStream; |
| @@ -20,14 +22,17 @@ import java.io.IOException; | |||
| 20 | import java.io.InputStream; | 22 | import java.io.InputStream; |
| 21 | import java.util.*; | 23 | import java.util.*; |
| 22 | import java.util.function.Consumer; | 24 | import java.util.function.Consumer; |
| 25 | import java.util.function.Function; | ||
| 23 | import java.util.jar.JarEntry; | 26 | import java.util.jar.JarEntry; |
| 24 | import java.util.jar.JarFile; | 27 | import java.util.jar.JarFile; |
| 25 | import java.util.jar.JarInputStream; | 28 | import java.util.jar.JarInputStream; |
| 26 | 29 | ||
| 27 | public class ParsedJar { | 30 | public class ParsedJar { |
| 28 | private final Map<String, ClassNode> nodes = new LinkedHashMap<>(); | 31 | private final Map<String, byte[]> classBytes; |
| 32 | private final Map<String, ClassNode> nodeCache = new HashMap<>(); | ||
| 29 | 33 | ||
| 30 | public ParsedJar(JarFile jar) throws IOException { | 34 | public ParsedJar(JarFile jar) throws IOException { |
| 35 | Map<String, byte[]> uClassBytes = new LinkedHashMap<>();; | ||
| 31 | try { | 36 | try { |
| 32 | // get the jar entries that correspond to classes | 37 | // get the jar entries that correspond to classes |
| 33 | Enumeration<JarEntry> entries = jar.entries(); | 38 | Enumeration<JarEntry> entries = jar.entries(); |
| @@ -36,60 +41,75 @@ public class ParsedJar { | |||
| 36 | // is this a class file? | 41 | // is this a class file? |
| 37 | if (entry.getName().endsWith(".class")) { | 42 | if (entry.getName().endsWith(".class")) { |
| 38 | try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) { | 43 | try (InputStream input = new BufferedInputStream(jar.getInputStream(entry))) { |
| 39 | // read the ClassNode from the jar | ||
| 40 | ClassReader reader = new ClassReader(input); | ||
| 41 | ClassNode node = new ClassNode(); | ||
| 42 | reader.accept(node, 0); | ||
| 43 | String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); | 44 | String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); |
| 44 | nodes.put(path, node); | 45 | uClassBytes.put(path, ByteStreams.toByteArray(input)); |
| 45 | } | 46 | } |
| 46 | } | 47 | } |
| 47 | } | 48 | } |
| 48 | } finally { | 49 | } finally { |
| 49 | jar.close(); | 50 | jar.close(); |
| 51 | classBytes = Collections.unmodifiableMap(uClassBytes); | ||
| 50 | } | 52 | } |
| 51 | } | 53 | } |
| 52 | 54 | ||
| 53 | public ParsedJar(JarInputStream jar) throws IOException { | 55 | public ParsedJar(JarInputStream jar) throws IOException { |
| 56 | Map<String, byte[]> uClassBytes = new LinkedHashMap<>(); | ||
| 54 | try { | 57 | try { |
| 55 | // get the jar entries that correspond to classes | 58 | // get the jar entries that correspond to classes |
| 56 | JarEntry entry; | 59 | JarEntry entry; |
| 57 | while ((entry = jar.getNextJarEntry()) != null) { | 60 | while ((entry = jar.getNextJarEntry()) != null) { |
| 58 | // is this a class file? | 61 | // is this a class file? |
| 59 | if (entry.getName().endsWith(".class")) { | 62 | if (entry.getName().endsWith(".class")) { |
| 60 | // read the ClassNode from the jar | ||
| 61 | ClassReader reader = new ClassReader(jar); | ||
| 62 | ClassNode node = new ClassNode(); | ||
| 63 | reader.accept(node, 0); | ||
| 64 | String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); | 63 | String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); |
| 65 | nodes.put(path, node); | 64 | uClassBytes.put(path, ByteStreams.toByteArray(jar)); |
| 66 | jar.closeEntry(); | 65 | jar.closeEntry(); |
| 67 | } | 66 | } |
| 68 | } | 67 | } |
| 69 | } finally { | 68 | } finally { |
| 70 | jar.close(); | 69 | jar.close(); |
| 70 | classBytes = Collections.unmodifiableMap(uClassBytes); | ||
| 71 | } | 71 | } |
| 72 | } | 72 | } |
| 73 | 73 | ||
| 74 | public void visit(Consumer<ClassNode> visitor) { | 74 | public void visitReader(Function<String, ClassVisitor> visitorFunction, int options) { |
| 75 | for (ClassNode node : nodes.values()) { | 75 | for (String s : classBytes.keySet()) { |
| 76 | visitor.accept(node); | 76 | ClassNode nodeCached = nodeCache.get(s); |
| 77 | if (nodeCached != null) { | ||
| 78 | nodeCached.accept(visitorFunction.apply(s)); | ||
| 79 | } else { | ||
| 80 | new ClassReader(classBytes.get(s)).accept(visitorFunction.apply(s), options); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | public void visitNode(Consumer<ClassNode> consumer) { | ||
| 86 | for (String s : classBytes.keySet()) { | ||
| 87 | consumer.accept(getClassNode(s)); | ||
| 77 | } | 88 | } |
| 78 | } | 89 | } |
| 79 | 90 | ||
| 80 | public int getClassCount() { | 91 | public int getClassCount() { |
| 81 | return nodes.size(); | 92 | return classBytes.size(); |
| 82 | } | 93 | } |
| 83 | 94 | ||
| 84 | public List<ClassEntry> getClassEntries() { | 95 | public List<ClassEntry> getClassEntries() { |
| 85 | List<ClassEntry> entries = new ArrayList<>(nodes.size()); | 96 | List<ClassEntry> entries = new ArrayList<>(classBytes.size()); |
| 86 | for (ClassNode node : nodes.values()) { | 97 | for (String s : classBytes.keySet()) { |
| 87 | entries.add(new ClassEntry(node.name)); | 98 | entries.add(new ClassEntry(s)); |
| 88 | } | 99 | } |
| 89 | return entries; | 100 | return entries; |
| 90 | } | 101 | } |
| 91 | 102 | ||
| 92 | public ClassNode getClassNode(String name) { | 103 | public ClassNode getClassNode(String name) { |
| 93 | return nodes.get(name); | 104 | return nodeCache.computeIfAbsent(name, (n) -> { |
| 105 | ClassReader reader = new ClassReader(classBytes.get(name)); | ||
| 106 | ClassNode node = new ClassNode(); | ||
| 107 | reader.accept(node, 0); | ||
| 108 | return node; | ||
| 109 | }); | ||
| 94 | } | 110 | } |
| 111 | |||
| 112 | public Map<String, byte[]> getClassDataMap() { | ||
| 113 | return classBytes; | ||
| 114 | } | ||
| 95 | } | 115 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java index 78195cb..4c84e69 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java | |||
| @@ -22,10 +22,7 @@ import com.strobel.decompiler.languages.java.ast.Identifier; | |||
| 22 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | 22 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; |
| 23 | import cuchaz.enigma.mapping.entry.Entry; | 23 | import cuchaz.enigma.mapping.entry.Entry; |
| 24 | 24 | ||
| 25 | import java.util.Collection; | 25 | import java.util.*; |
| 26 | import java.util.List; | ||
| 27 | import java.util.Map; | ||
| 28 | import java.util.TreeMap; | ||
| 29 | import java.util.regex.Pattern; | 26 | import java.util.regex.Pattern; |
| 30 | 27 | ||
| 31 | public class SourceIndex { | 28 | public class SourceIndex { |
| @@ -48,9 +45,12 @@ public class SourceIndex { | |||
| 48 | this.tokenToReference = Maps.newTreeMap(); | 45 | this.tokenToReference = Maps.newTreeMap(); |
| 49 | this.referenceToTokens = HashMultimap.create(); | 46 | this.referenceToTokens = HashMultimap.create(); |
| 50 | this.declarationToToken = Maps.newHashMap(); | 47 | this.declarationToToken = Maps.newHashMap(); |
| 51 | this.lineOffsets = Lists.newArrayList(); | 48 | calculateLineOffsets(); |
| 49 | } | ||
| 52 | 50 | ||
| 51 | private void calculateLineOffsets() { | ||
| 53 | // count the lines | 52 | // count the lines |
| 53 | this.lineOffsets = Lists.newArrayList(); | ||
| 54 | this.lineOffsets.add(0); | 54 | this.lineOffsets.add(0); |
| 55 | for (int i = 0; i < source.length(); i++) { | 55 | for (int i = 0; i < source.length(); i++) { |
| 56 | if (source.charAt(i) == '\n') { | 56 | if (source.charAt(i) == '\n') { |
| @@ -59,6 +59,32 @@ public class SourceIndex { | |||
| 59 | } | 59 | } |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | public void remap(String source, Map<Token, Token> tokenMap) { | ||
| 63 | this.source = source; | ||
| 64 | calculateLineOffsets(); | ||
| 65 | |||
| 66 | for (Entry entry : Lists.newArrayList(declarationToToken.keySet())) { | ||
| 67 | Token token = declarationToToken.get(entry); | ||
| 68 | declarationToToken.put(entry, tokenMap.getOrDefault(token, token)); | ||
| 69 | } | ||
| 70 | |||
| 71 | for (Token token : Lists.newArrayList(tokenToReference.keySet())) { | ||
| 72 | EntryReference<Entry, Entry> e = tokenToReference.remove(token); | ||
| 73 | tokenToReference.put(tokenMap.getOrDefault(token, token), e); | ||
| 74 | } | ||
| 75 | |||
| 76 | for (EntryReference<Entry, Entry> ref : Lists.newArrayList(referenceToTokens.keySet())) { | ||
| 77 | List<Token> newTokens = new ArrayList<>(); | ||
| 78 | |||
| 79 | for (Token token : referenceToTokens.get(ref)) { | ||
| 80 | newTokens.add(tokenMap.getOrDefault(token, token)); | ||
| 81 | } | ||
| 82 | |||
| 83 | referenceToTokens.removeAll(ref); | ||
| 84 | referenceToTokens.putAll(ref, newTokens); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 62 | public String getSource() { | 88 | public String getSource() { |
| 63 | return this.source; | 89 | return this.source; |
| 64 | } | 90 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java index 266d202..14fa7ca 100644 --- a/src/main/java/cuchaz/enigma/analysis/Token.java +++ b/src/main/java/cuchaz/enigma/analysis/Token.java | |||
| @@ -25,6 +25,25 @@ public class Token implements Comparable<Token> { | |||
| 25 | } | 25 | } |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | public int getRenameOffset(String to) { | ||
| 29 | int length = this.end - this.start; | ||
| 30 | return to.length() - length; | ||
| 31 | } | ||
| 32 | |||
| 33 | public String rename(String source, String to) { | ||
| 34 | int oldEnd = this.end; | ||
| 35 | this.text = to; | ||
| 36 | this.end = this.start + to.length(); | ||
| 37 | |||
| 38 | return source.substring(0, this.start) + to + source.substring(oldEnd); | ||
| 39 | } | ||
| 40 | |||
| 41 | public Token move(int offset) { | ||
| 42 | Token token = new Token(this.start + offset, this.end + offset, null); | ||
| 43 | token.text = text; | ||
| 44 | return token; | ||
| 45 | } | ||
| 46 | |||
| 28 | public boolean contains(int pos) { | 47 | public boolean contains(int pos) { |
| 29 | return pos >= start && pos <= end; | 48 | return pos >= start && pos <= end; |
| 30 | } | 49 | } |
| @@ -41,7 +60,7 @@ public class Token implements Comparable<Token> { | |||
| 41 | 60 | ||
| 42 | @Override | 61 | @Override |
| 43 | public int hashCode() { | 62 | public int hashCode() { |
| 44 | return Integer.hashCode(start) + Integer.hashCode(end) + (text != null ? text.hashCode() : 0); | 63 | return start * 37 + end; |
| 45 | } | 64 | } |
| 46 | 65 | ||
| 47 | public boolean equals(Token other) { | 66 | public boolean equals(Token other) { |