From 2b2249e873c4adfd2dd6e8f1f2489ccd9f6aa021 Mon Sep 17 00:00:00 2001 From: gegy1000 Date: Sat, 19 May 2018 17:02:46 +0200 Subject: Initial port to ASM --- src/main/java/cuchaz/enigma/CommandMain.java | 2 +- src/main/java/cuchaz/enigma/Deobfuscator.java | 225 ++++---- .../java/cuchaz/enigma/TranslatingTypeLoader.java | 143 ++--- src/main/java/cuchaz/enigma/analysis/Access.java | 11 +- .../enigma/analysis/BehaviorReferenceTreeNode.java | 93 ---- .../java/cuchaz/enigma/analysis/BridgeMarker.java | 38 -- .../analysis/ClassImplementationsTreeNode.java | 8 +- .../enigma/analysis/ClassInheritanceTreeNode.java | 14 +- .../cuchaz/enigma/analysis/EntryReference.java | 14 +- .../java/cuchaz/enigma/analysis/EntryRenamer.java | 47 +- .../enigma/analysis/FieldReferenceTreeNode.java | 26 +- .../cuchaz/enigma/analysis/IndexClassVisitor.java | 38 ++ .../enigma/analysis/IndexInnerClassVisitor.java | 23 + .../enigma/analysis/IndexReferenceVisitor.java | 50 ++ .../cuchaz/enigma/analysis/JarClassIterator.java | 123 ----- src/main/java/cuchaz/enigma/analysis/JarIndex.java | 588 +++++---------------- .../analysis/MethodImplementationsTreeNode.java | 9 +- .../enigma/analysis/MethodInheritanceTreeNode.java | 12 +- .../enigma/analysis/MethodReferenceTreeNode.java | 91 ++++ .../java/cuchaz/enigma/analysis/ParsedJar.java | 72 +++ .../analysis/SourceIndexBehaviorVisitor.java | 204 ------- .../enigma/analysis/SourceIndexClassVisitor.java | 36 +- .../enigma/analysis/SourceIndexMethodVisitor.java | 203 +++++++ .../cuchaz/enigma/analysis/SourceIndexVisitor.java | 8 +- .../cuchaz/enigma/analysis/TranslationIndex.java | 123 ++--- .../java/cuchaz/enigma/bytecode/AccessFlags.java | 79 +++ .../cuchaz/enigma/bytecode/ClassProtectifier.java | 54 +- .../cuchaz/enigma/bytecode/ClassPublifier.java | 58 +- .../java/cuchaz/enigma/bytecode/ClassRenamer.java | 539 ------------------- .../cuchaz/enigma/bytecode/ConstPoolEditor.java | 264 --------- src/main/java/cuchaz/enigma/bytecode/InfoType.java | 266 ---------- .../enigma/bytecode/MethodParametersAttribute.java | 87 --- .../bytecode/accessors/ClassInfoAccessor.java | 56 -- .../bytecode/accessors/ConstInfoAccessor.java | 124 ----- .../accessors/InvokeDynamicInfoAccessor.java | 75 --- .../bytecode/accessors/MemberRefInfoAccessor.java | 75 --- .../accessors/MethodHandleInfoAccessor.java | 75 --- .../bytecode/accessors/MethodTypeInfoAccessor.java | 57 -- .../accessors/NameAndTypeInfoAccessor.java | 75 --- .../bytecode/accessors/StringInfoAccessor.java | 56 -- .../bytecode/accessors/Utf8InfoAccessor.java | 29 - .../bytecode/translators/ClassTranslator.java | 161 ------ .../bytecode/translators/InnerClassWriter.java | 144 ----- .../translators/LocalVariableTranslator.java | 142 ----- .../translators/MethodParameterTranslator.java | 62 --- .../translators/TranslationClassVisitor.java | 106 ++++ .../translators/TranslationMethodVisitor.java | 82 +++ src/main/java/cuchaz/enigma/gui/CodeReader.java | 2 +- src/main/java/cuchaz/enigma/gui/Gui.java | 66 +-- src/main/java/cuchaz/enigma/gui/GuiController.java | 18 +- .../enigma/gui/node/ClassSelectorPackageNode.java | 4 +- .../java/cuchaz/enigma/mapping/ArgumentEntry.java | 110 ---- .../cuchaz/enigma/mapping/ArgumentMapping.java | 50 -- .../java/cuchaz/enigma/mapping/BehaviorEntry.java | 16 - .../java/cuchaz/enigma/mapping/ClassDefEntry.java | 29 + .../java/cuchaz/enigma/mapping/ClassEntry.java | 26 +- .../java/cuchaz/enigma/mapping/ClassMapping.java | 163 +++--- .../cuchaz/enigma/mapping/ClassNameReplacer.java | 16 - .../cuchaz/enigma/mapping/ConstructorEntry.java | 105 ---- .../enigma/mapping/DirectionalTranslator.java | 319 +++++++++++ src/main/java/cuchaz/enigma/mapping/Entry.java | 4 +- .../java/cuchaz/enigma/mapping/EntryFactory.java | 101 +--- .../java/cuchaz/enigma/mapping/FieldDefEntry.java | 34 ++ .../java/cuchaz/enigma/mapping/FieldEntry.java | 51 +- .../java/cuchaz/enigma/mapping/FieldMapping.java | 36 +- .../enigma/mapping/LocalVariableDefEntry.java | 75 +++ .../cuchaz/enigma/mapping/LocalVariableEntry.java | 65 +-- .../enigma/mapping/LocalVariableMapping.java | 50 ++ src/main/java/cuchaz/enigma/mapping/Mappings.java | 45 +- .../cuchaz/enigma/mapping/MappingsChecker.java | 10 +- .../enigma/mapping/MappingsEnigmaReader.java | 18 +- .../enigma/mapping/MappingsEnigmaWriter.java | 16 +- .../cuchaz/enigma/mapping/MappingsRenamer.java | 163 +++--- .../cuchaz/enigma/mapping/MappingsSRGWriter.java | 6 +- .../cuchaz/enigma/mapping/MappingsTinyReader.java | 4 +- .../java/cuchaz/enigma/mapping/MethodDefEntry.java | 35 ++ .../cuchaz/enigma/mapping/MethodDescriptor.java | 113 ++++ .../java/cuchaz/enigma/mapping/MethodEntry.java | 59 +-- .../java/cuchaz/enigma/mapping/MethodMapping.java | 130 +++-- .../java/cuchaz/enigma/mapping/NameValidator.java | 15 +- .../cuchaz/enigma/mapping/ProcyonEntryFactory.java | 36 +- .../cuchaz/enigma/mapping/ReferencedEntryPool.java | 50 ++ src/main/java/cuchaz/enigma/mapping/Signature.java | 106 ---- .../enigma/mapping/TranslationDirection.java | 10 +- .../java/cuchaz/enigma/mapping/Translator.java | 344 ++---------- .../java/cuchaz/enigma/mapping/TypeDescriptor.java | 240 +++++++++ src/test/java/cuchaz/enigma/TestDeobfed.java | 6 +- src/test/java/cuchaz/enigma/TestEntryFactory.java | 28 +- src/test/java/cuchaz/enigma/TestInnerClasses.java | 47 +- .../enigma/TestJarIndexConstructorReferences.java | 78 ++- .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 111 ++-- .../java/cuchaz/enigma/TestJarIndexLoneClass.java | 37 +- .../java/cuchaz/enigma/TestMethodDescriptor.java | 247 +++++++++ src/test/java/cuchaz/enigma/TestSignature.java | 270 ---------- .../java/cuchaz/enigma/TestTokensConstructors.java | 90 ++-- .../java/cuchaz/enigma/TestTypeDescriptor.java | 243 +++++++++ 96 files changed, 3320 insertions(+), 5444 deletions(-) delete mode 100644 src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/BridgeMarker.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/JarClassIterator.java create mode 100644 src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java create mode 100644 src/main/java/cuchaz/enigma/analysis/ParsedJar.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java create mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/AccessFlags.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/InfoType.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java delete mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/ClassDefEntry.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java create mode 100644 src/main/java/cuchaz/enigma/mapping/FieldDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/LocalVariableDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java create mode 100644 src/main/java/cuchaz/enigma/mapping/MethodDefEntry.java create mode 100644 src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java create mode 100644 src/main/java/cuchaz/enigma/mapping/ReferencedEntryPool.java delete mode 100644 src/main/java/cuchaz/enigma/mapping/Signature.java create mode 100644 src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java create mode 100644 src/test/java/cuchaz/enigma/TestMethodDescriptor.java delete mode 100644 src/test/java/cuchaz/enigma/TestSignature.java create mode 100644 src/test/java/cuchaz/enigma/TestTypeDescriptor.java (limited to 'src') diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index f546eb1..59eb1b6 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java @@ -99,7 +99,7 @@ public class CommandMain { private static void convertMappings(String[] args) throws Exception { File fileMappings = getReadableFile(getArg(args, 1, "enigma mapping", true)); File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); - String name = getArg(args, 3, "format type", true); + String name = getArg(args, 3, "format desc", true); Mappings.FormatType formatType; try { formatType = Mappings.FormatType.valueOf(name.toUpperCase()); diff --git a/src/main/java/cuchaz/enigma/Deobfuscator.java b/src/main/java/cuchaz/enigma/Deobfuscator.java index 1e99af2..e1454c7 100644 --- a/src/main/java/cuchaz/enigma/Deobfuscator.java +++ b/src/main/java/cuchaz/enigma/Deobfuscator.java @@ -31,50 +31,55 @@ import cuchaz.enigma.bytecode.ClassPublifier; import cuchaz.enigma.mapping.*; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.utils.Utils; -import javassist.CtClass; -import javassist.bytecode.Descriptor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; import java.io.*; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.jar.JarOutputStream; public class Deobfuscator { + private final ReferencedEntryPool entryPool = new ReferencedEntryPool(); private final JarFile jar; + private final ParsedJar parsedJar; private final DecompilerSettings settings; private final JarIndex jarIndex; private final MappingsRenamer renamer; private final Map translatorCache; private Mappings mappings; - public Deobfuscator(JarFile jar) { + public Deobfuscator(JarFile jar) throws IOException { this.jar = jar; + this.parsedJar = new ParsedJar(jar); // build the jar index - this.jarIndex = new JarIndex(); - this.jarIndex.indexJar(this.jar, true); + this.jarIndex = new JarIndex(entryPool); + this.jarIndex.indexJar(this.parsedJar, true); // config the decompiler this.settings = DecompilerSettings.javaDefaults(); this.settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); this.settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); this.settings.setForceExplicitTypeArguments( - Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); + Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); // DEBUG this.settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); this.settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); // init defaults this.translatorCache = Maps.newTreeMap(); - this.renamer = new MappingsRenamer(this.jarIndex, null); + this.renamer = new MappingsRenamer(this.jarIndex, null, this.entryPool); // init mappings setMappings(new Mappings()); } - public JarFile getJar() { - return this.jar; + public ParsedJar getJar() { + return this.parsedJar; } public String getJarName() { @@ -102,16 +107,16 @@ public class Deobfuscator { MappingsChecker checker = new MappingsChecker(this.jarIndex); checker.dropBrokenMappings(val); if (warnAboutDrops) { - for (java.util.Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedClassMappings().entrySet()) { System.out.println("WARNING: Couldn't find class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } - for (java.util.Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedInnerClassMappings().entrySet()) { System.out.println("WARNING: Couldn't find inner class entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } - for (java.util.Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedFieldMappings().entrySet()) { System.out.println("WARNING: Couldn't find field entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } - for (java.util.Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { + for (Map.Entry mapping : checker.getDroppedMethodMappings().entrySet()) { System.out.println("WARNING: Couldn't find behavior entry " + mapping.getKey() + " (" + mapping.getValue().getDeobfName() + ") in jar. Mapping was dropped."); } } @@ -123,7 +128,7 @@ public class Deobfuscator { public Translator getTranslator(TranslationDirection direction) { return this.translatorCache.computeIfAbsent(direction, - k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); + k -> this.mappings.getTranslator(direction, this.jarIndex.getTranslationIndex())); } public void getSeparatedClasses(List obfClasses, List deobfClasses) { @@ -150,10 +155,11 @@ public class Deobfuscator { public TranslatingTypeLoader createTypeLoader() { return new TranslatingTypeLoader( - this.jar, - this.jarIndex, - getTranslator(TranslationDirection.Obfuscating), - getTranslator(TranslationDirection.Deobfuscating) + this.parsedJar, + this.jarIndex, + this.entryPool, + getTranslator(TranslationDirection.OBFUSCATING), + getTranslator(TranslationDirection.DEOBFUSCATING) ); } @@ -172,15 +178,15 @@ public class Deobfuscator { deobfClassName = classMapping.getDeobfName(); } - // set the type loader + // set the desc loader TranslatingTypeLoader loader = createTypeLoader(); this.settings.setTypeLoader(loader); - // see if procyon can find the type + // see if procyon can find the desc TypeReference type = new MetadataSystem(loader).lookupType(deobfClassName); if (type == null) { - throw new Error(String.format("Unable to find type: %s (deobf: %s)\nTried class names: %s", - className, deobfClassName, loader.getClassNamesToTry(deobfClassName) + throw new Error(String.format("Unable to find desc: %s (deobf: %s)\nTried class names: %s", + className, deobfClassName, loader.getClassNamesToTry(deobfClassName) )); } TypeDefinition resolvedType = type.resolve(); @@ -208,7 +214,7 @@ public class Deobfuscator { } else { index = new SourceIndex(source); } - sourceTree.acceptVisitor(new SourceIndexVisitor(), index); + sourceTree.acceptVisitor(new SourceIndexVisitor(entryPool), index); // DEBUG // sourceTree.acceptVisitor( new TreeDumpVisitor( new File( "tree.txt" ) ), null ); @@ -221,10 +227,10 @@ public class Deobfuscator { Entry obfEntry = obfuscateEntry(deobfReference.entry); // try to resolve the class - ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryClass(obfEntry); - if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getClassEntry())) { + ClassEntry resolvedObfClassEntry = this.jarIndex.getTranslationIndex().resolveEntryOwner(obfEntry); + if (resolvedObfClassEntry != null && !resolvedObfClassEntry.equals(obfEntry.getOwnerClassEntry())) { // change the class of the entry - obfEntry = obfEntry.cloneToNewClass(resolvedObfClassEntry); + obfEntry = obfEntry.updateOwnership(resolvedObfClassEntry); // save the new deobfuscated reference deobfReference.entry = deobfuscateEntry(obfEntry); @@ -305,18 +311,14 @@ public class Deobfuscator { } } - private boolean isBehaviorProvider(ClassEntry classObfEntry, BehaviorEntry behaviorEntry) { - if (behaviorEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) behaviorEntry; + private boolean isMethodProvider(ClassEntry classObfEntry, MethodEntry methodEntry) { + Set classEntries = new HashSet<>(); + addAllPotentialAncestors(classEntries, classObfEntry); - Set classEntries = new HashSet<>(); - addAllPotentialAncestors(classEntries, classObfEntry); - - for (ClassEntry parentEntry : classEntries) { - MethodEntry ancestorMethodEntry = new MethodEntry(parentEntry, methodEntry.getName(), methodEntry.getSignature()); - if (jarIndex.containsObfBehavior(ancestorMethodEntry)) { - return false; - } + for (ClassEntry parentEntry : classEntries) { + MethodEntry ancestorMethodEntry = entryPool.getMethod(parentEntry, methodEntry.getName(), methodEntry.getDesc()); + if (jarIndex.containsObfMethod(ancestorMethodEntry)) { + return false; } } @@ -332,7 +334,7 @@ public class Deobfuscator { for (ClassMapping classMapping : Lists.newArrayList(getMappings().classes())) { progress.onProgress(i++, classMapping.getDeobfName()); rebuildMethodNames(classMapping, renameClassMap); - for(ClassMapping innerClass : classMapping.innerClasses()){ + for (ClassMapping innerClass : classMapping.innerClasses()) { rebuildMethodNames(innerClass, renameClassMap); } } @@ -356,29 +358,29 @@ public class Deobfuscator { try { rename(obfEntry, name); - } catch (IllegalNameException exception) - { + } catch (IllegalNameException exception) { System.out.println("WARNING: " + exception.getMessage()); } } } } - private void rebuildMethodNames(ClassMapping classMapping, Map> renameClassMap){ + private void rebuildMethodNames(ClassMapping classMapping, Map> renameClassMap) { Map renameEntries = new HashMap<>(); for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { ClassEntry classObfEntry = classMapping.getObfEntry(); - BehaviorEntry obfEntry = methodMapping.getObfEntry(classObfEntry); + MethodEntry obfEntry = methodMapping.getObfEntry(classObfEntry); - if (isBehaviorProvider(classObfEntry, obfEntry)) { - if (hasDeobfuscatedName(obfEntry) && !(obfEntry instanceof ConstructorEntry) - && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { + if (isMethodProvider(classObfEntry, obfEntry)) { + if (hasDeobfuscatedName(obfEntry) + && !(methodMapping.getDeobfName().equals(methodMapping.getObfName()))) { renameEntries.put(obfEntry, methodMapping.getDeobfName()); } - for (ArgumentMapping argumentMapping : Lists.newArrayList(methodMapping.arguments())) { - Entry argObfEntry = argumentMapping.getObfEntry(obfEntry); + ArrayList arguments = Lists.newArrayList(methodMapping.arguments()); + for (LocalVariableMapping localVariableMapping : arguments) { + Entry argObfEntry = localVariableMapping.getObfEntry(obfEntry); if (hasDeobfuscatedName(argObfEntry)) { renameEntries.put(argObfEntry, deobfuscateEntry(argObfEntry).getName()); } @@ -390,45 +392,44 @@ public class Deobfuscator { } - public void writeJar(File out, ProgressListener progress) { - transformJar(out, progress, createTypeLoader()::transformClass); + transformJar(out, progress, createTypeLoader()::createTransformer); } public void protectifyJar(File out, ProgressListener progress) { - transformJar(out, progress, ClassProtectifier::protectify); + transformJar(out, progress, (node, writer) -> node.accept(new ClassProtectifier(Opcodes.ASM5, writer))); } public void publifyJar(File out, ProgressListener progress) { - transformJar(out, progress, ClassPublifier::publify); + transformJar(out, progress, (node, writer) -> node.accept(new ClassPublifier(Opcodes.ASM5, writer))); } public void transformJar(File out, ProgressListener progress, ClassTransformer transformer) { try (JarOutputStream outJar = new JarOutputStream(new FileOutputStream(out))) { if (progress != null) { - progress.init(JarClassIterator.getClassEntries(this.jar).size(), "Transforming classes..."); + progress.init(parsedJar.getClassCount(), "Transforming classes..."); } - int i = 0; - for (CtClass c : JarClassIterator.classes(this.jar)) { + AtomicInteger i = new AtomicInteger(); + parsedJar.visit(node -> { if (progress != null) { - progress.onProgress(i++, c.getName()); + progress.onProgress(i.getAndIncrement(), node.name); } try { - c = transformer.transform(c); - outJar.putNextEntry(new JarEntry(c.getName().replace('.', '/') + ".class")); - outJar.write(c.toBytecode()); + ClassWriter writer = new ClassWriter(0); + transformer.write(node, writer); + outJar.putNextEntry(new JarEntry(node.name.replace('.', '/') + ".class")); + outJar.write(writer.toByteArray()); outJar.closeEntry(); } catch (Throwable t) { - throw new Error("Unable to transform class " + c.getName(), t); + throw new Error("Unable to transform class " + node.name, t); } - } + }); + if (progress != null) { - progress.onProgress(i, "Done!"); + progress.onProgress(i.get(), "Done!"); } - - outJar.close(); } catch (IOException ex) { throw new Error("Unable to write to Jar file!"); } @@ -438,14 +439,22 @@ public class Deobfuscator { if (deobfEntry == null) { return null; } - return getTranslator(TranslationDirection.Obfuscating).translateEntry(deobfEntry); + T translatedEntry = getTranslator(TranslationDirection.OBFUSCATING).getTranslatedEntry(deobfEntry); + if (translatedEntry == null) { + return deobfEntry; + } + return translatedEntry; } public T deobfuscateEntry(T obfEntry) { if (obfEntry == null) { return null; } - return getTranslator(TranslationDirection.Deobfuscating).translateEntry(obfEntry); + T translatedEntry = getTranslator(TranslationDirection.DEOBFUSCATING).getTranslatedEntry(obfEntry); + if (translatedEntry == null) { + return obfEntry; + } + return translatedEntry; } public EntryReference obfuscateReference(EntryReference deobfReference) { @@ -473,7 +482,7 @@ public class Deobfuscator { // HACKHACK: Object methods are not obfuscated identifiers MethodEntry obfMethodEntry = (MethodEntry) obfEntry; String name = obfMethodEntry.getName(); - String sig = obfMethodEntry.getSignature().toString(); + String sig = obfMethodEntry.getDesc().toString(); if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) { return false; } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) { @@ -499,7 +508,7 @@ public class Deobfuscator { } // FIXME: HACK EVEN MORE HACK! - if (hack && this.jarIndex.containsObfEntry(obfEntry.getClassEntry())) + if (hack && this.jarIndex.containsObfEntry(obfEntry.getOwnerClassEntry())) return true; } @@ -515,27 +524,24 @@ public class Deobfuscator { } public boolean hasDeobfuscatedName(Entry obfEntry) { - Translator translator = getTranslator(TranslationDirection.Deobfuscating); + Translator translator = getTranslator(TranslationDirection.DEOBFUSCATING); if (obfEntry instanceof ClassEntry) { ClassEntry obfClass = (ClassEntry) obfEntry; List mappingChain = this.mappings.getClassMappingChain(obfClass); ClassMapping classMapping = mappingChain.get(mappingChain.size() - 1); return classMapping != null && classMapping.getDeobfName() != null; } else if (obfEntry instanceof FieldEntry) { - return translator.translate((FieldEntry) obfEntry) != null; + return translator.getTranslatedField((FieldEntry) obfEntry) != null; } else if (obfEntry instanceof MethodEntry) { - return translator.translate((MethodEntry) obfEntry) != null; - } else if (obfEntry instanceof ConstructorEntry) { - // constructors have no names - return false; - } else if (obfEntry instanceof ArgumentEntry) { - return translator.translate((ArgumentEntry) obfEntry) != null; + MethodEntry methodEntry = (MethodEntry) obfEntry; + if (methodEntry.isConstructor()) { + return false; + } + return translator.getTranslatedMethod(methodEntry) != null; } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - //return translator.translate((LocalVariableEntry)obfEntry) != null; - return false; + return translator.getTranslatedVariable((LocalVariableEntry) obfEntry) != null; } else { - throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); } } @@ -547,19 +553,18 @@ public class Deobfuscator { public void rename(Entry obfEntry, String newName, boolean clearCache) { if (obfEntry instanceof ClassEntry) { - this.renamer.setClassName((ClassEntry) obfEntry, Descriptor.toJvmName(newName)); + this.renamer.setClassName((ClassEntry) obfEntry, newName); } else if (obfEntry instanceof FieldEntry) { this.renamer.setFieldName((FieldEntry) obfEntry, newName); } else if (obfEntry instanceof MethodEntry) { + if (((MethodEntry) obfEntry).isConstructor()) { + throw new IllegalArgumentException("Cannot rename constructors"); + } this.renamer.setMethodTreeName((MethodEntry) obfEntry, newName); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.setArgumentTreeName((ArgumentEntry) obfEntry, newName); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it + this.renamer.setLocalVariableTreeName((LocalVariableEntry) obfEntry, newName); } else { - throw new Error("Unknown entry type: " + obfEntry.getClass().getName()); + throw new Error("Unknown entry desc: " + obfEntry.getClass().getName()); } // clear caches @@ -573,13 +578,14 @@ public class Deobfuscator { } else if (obfEntry instanceof FieldEntry) { this.renamer.removeFieldMapping((FieldEntry) obfEntry); } else if (obfEntry instanceof MethodEntry) { + if (((MethodEntry) obfEntry).isConstructor()) { + throw new IllegalArgumentException("Cannot rename constructors"); + } this.renamer.removeMethodTreeMapping((MethodEntry) obfEntry); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.removeArgumentMapping((ArgumentEntry) obfEntry); + } else if (obfEntry instanceof LocalVariableEntry) { + this.renamer.removeLocalVariableMapping((LocalVariableEntry) obfEntry); } else { - throw new Error("Unknown entry type: " + obfEntry); + throw new Error("Unknown entry desc: " + obfEntry); } // clear caches @@ -592,15 +598,15 @@ public class Deobfuscator { } else if (obfEntry instanceof FieldEntry) { this.renamer.markFieldAsDeobfuscated((FieldEntry) obfEntry); } else if (obfEntry instanceof MethodEntry) { - this.renamer.markMethodTreeAsDeobfuscated((MethodEntry) obfEntry); - } else if (obfEntry instanceof ConstructorEntry) { - throw new IllegalArgumentException("Cannot rename constructors"); - } else if (obfEntry instanceof ArgumentEntry) { - this.renamer.markArgumentAsDeobfuscated((ArgumentEntry) obfEntry); + MethodEntry methodEntry = (MethodEntry) obfEntry; + if (methodEntry.isConstructor()) { + throw new IllegalArgumentException("Cannot rename constructors"); + } + this.renamer.markMethodTreeAsDeobfuscated(methodEntry); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it + this.renamer.markArgumentAsDeobfuscated((LocalVariableEntry) obfEntry); } else { - throw new Error("Unknown entry type: " + obfEntry); + throw new Error("Unknown entry desc: " + obfEntry); } // clear caches @@ -613,17 +619,24 @@ public class Deobfuscator { this.renamer.setClassModifier((ClassEntry) obfEntry, modifierEntry); else if (obfEntry instanceof FieldEntry) this.renamer.setFieldModifier((FieldEntry) obfEntry, modifierEntry); - else if (obfEntry instanceof BehaviorEntry) - this.renamer.setMethodModifier((BehaviorEntry) obfEntry, modifierEntry); + else if (obfEntry instanceof MethodEntry) + this.renamer.setMethodModifier((MethodEntry) obfEntry, modifierEntry); else - throw new Error("Unknown entry type: " + obfEntry); + throw new Error("Unknown entry desc: " + obfEntry); } - public Mappings.EntryModifier getModifier(Entry obEntry) { - Entry entry = obfuscateEntry(obEntry); + public Mappings.EntryModifier getModifier(Entry obfEntry) { + Entry entry = obfuscateEntry(obfEntry); if (entry != null) - obEntry = entry; - return getTranslator(TranslationDirection.Deobfuscating).getModifier(obEntry); + obfEntry = entry; + if (obfEntry instanceof ClassEntry) + return this.renamer.getClassModifier((ClassEntry) obfEntry); + else if (obfEntry instanceof FieldEntry) + return this.renamer.getFieldModifier((FieldEntry) obfEntry); + else if (obfEntry instanceof MethodEntry) + return this.renamer.getMethodModfifier((MethodEntry) obfEntry); + else + throw new Error("Unknown entry desc: " + obfEntry); } public interface ProgressListener { @@ -633,6 +646,6 @@ public class Deobfuscator { } public interface ClassTransformer { - CtClass transform(CtClass c) throws Exception; + void write(ClassNode node, ClassWriter writer); } } diff --git a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java index 2a2041a..c91070f 100644 --- a/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java +++ b/src/main/java/cuchaz/enigma/TranslatingTypeLoader.java @@ -16,37 +16,33 @@ import com.google.common.collect.Maps; import com.strobel.assembler.metadata.Buffer; import com.strobel.assembler.metadata.ClasspathTypeLoader; import com.strobel.assembler.metadata.ITypeLoader; -import cuchaz.enigma.analysis.BridgeMarker; import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.bytecode.translators.ClassTranslator; -import cuchaz.enigma.bytecode.translators.InnerClassWriter; -import cuchaz.enigma.bytecode.translators.LocalVariableTranslator; -import cuchaz.enigma.bytecode.translators.MethodParameterTranslator; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ReferencedEntryPool; import cuchaz.enigma.mapping.Translator; -import javassist.*; -import javassist.bytecode.Descriptor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; import java.util.List; import java.util.Map; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; public class TranslatingTypeLoader implements ITypeLoader { - private JarFile jar; - private JarIndex jarIndex; - private Translator obfuscatingTranslator; - private Translator deobfuscatingTranslator; - private Map cache; - private ClasspathTypeLoader defaultTypeLoader; + private final ParsedJar jar; + private final JarIndex jarIndex; + private final ReferencedEntryPool entryPool; + private final Translator obfuscatingTranslator; + private final Translator deobfuscatingTranslator; + private final Map cache; + private final ClasspathTypeLoader defaultTypeLoader; - public TranslatingTypeLoader(JarFile jar, JarIndex jarIndex, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { + public TranslatingTypeLoader(ParsedJar jar, JarIndex jarIndex, ReferencedEntryPool entryPool, Translator obfuscatingTranslator, Translator deobfuscatingTranslator) { this.jar = jar; this.jarIndex = jarIndex; + this.entryPool = entryPool; this.obfuscatingTranslator = obfuscatingTranslator; this.deobfuscatingTranslator = deobfuscatingTranslator; this.cache = Maps.newHashMap(); @@ -71,7 +67,7 @@ public class TranslatingTypeLoader implements ITypeLoader { } if (data == null) { - // chain to default type loader + // chain to default desc loader return this.defaultTypeLoader.tryLoadType(className, out); } @@ -82,36 +78,18 @@ public class TranslatingTypeLoader implements ITypeLoader { return true; } - public CtClass loadClass(String deobfClassName) { - - byte[] data = loadType(deobfClassName); - if (data == null) { - return null; - } - - // return a javassist handle for the class - String javaClassFileName = Descriptor.toJavaName(deobfClassName); - ClassPool classPool = new ClassPool(); - classPool.insertClassPath(new ByteArrayClassPath(javaClassFileName, data)); - try { - return classPool.get(javaClassFileName); - } catch (NotFoundException ex) { - throw new Error(ex); - } - } - private byte[] loadType(String className) { // NOTE: don't know if class name is obf or deobf ClassEntry classEntry = new ClassEntry(className); - ClassEntry obfClassEntry = this.obfuscatingTranslator.translateEntry(classEntry); + ClassEntry obfClassEntry = this.obfuscatingTranslator.getTranslatedClass(classEntry); // is this an inner class referenced directly? (ie trying to load b instead of a$b) if (!obfClassEntry.isInnerClass()) { List classChain = this.jarIndex.getObfClassChain(obfClassEntry); if (classChain.size() > 1) { System.err.println(String.format("WARNING: no class %s after inner class reconstruction. Try %s", - className, obfClassEntry.buildClassEntry(classChain) + className, obfClassEntry.buildClassEntry(classChain) )); return null; } @@ -126,56 +104,26 @@ public class TranslatingTypeLoader implements ITypeLoader { //System.out.println(String.format("Looking for %s (obf: %s)", classEntry.getName(), obfClassEntry.getName())); // find the class in the jar - String classInJarName = findClassInJar(obfClassEntry); - if (classInJarName == null) { + ClassNode node = findClassInJar(obfClassEntry); + if (node == null) { // couldn't find it return null; } - try { - // read the class file into a buffer - ByteArrayOutputStream data = new ByteArrayOutputStream(); - byte[] buf = new byte[1024 * 1024]; // 1 KiB - InputStream in = this.jar.getInputStream(this.jar.getJarEntry(classInJarName + ".class")); - while (true) { - int bytesRead = in.read(buf); - if (bytesRead <= 0) { - break; - } - data.write(buf, 0, bytesRead); - } - data.close(); - in.close(); - buf = data.toByteArray(); - - // load the javassist handle to the raw class - ClassPool classPool = new ClassPool(); - String classInJarJavaName = Descriptor.toJavaName(classInJarName); - classPool.insertClassPath(new ByteArrayClassPath(classInJarJavaName, buf)); - CtClass c = classPool.get(classInJarJavaName); - - c = transformClass(c); + ClassWriter writer = new ClassWriter(0); + createTransformer(node, writer); - // sanity checking - assertClassName(c, classEntry); - - // DEBUG - //Util.writeClass( c ); - - // we have a transformed class! - return c.toBytecode(); - } catch (IOException | NotFoundException | CannotCompileException ex) { - throw new Error(ex); - } + // we have a transformed class! + return writer.toByteArray(); } - private String findClassInJar(ClassEntry obfClassEntry) { + private ClassNode findClassInJar(ClassEntry obfClassEntry) { // try to find the class in the jar for (String className : getClassNamesToTry(obfClassEntry)) { - JarEntry jarEntry = this.jar.getJarEntry(className + ".class"); - if (jarEntry != null) { - return className; + ClassNode node = this.jar.getClassNode(className); + if (node != null) { + return node; } } @@ -184,7 +132,7 @@ public class TranslatingTypeLoader implements ITypeLoader { } public List getClassNamesToTry(String className) { - return getClassNamesToTry(this.obfuscatingTranslator.translateEntry(new ClassEntry(className))); + return getClassNamesToTry(this.obfuscatingTranslator.getTranslatedClass(new ClassEntry(className))); } public List getClassNamesToTry(ClassEntry obfClassEntry) { @@ -197,36 +145,7 @@ public class TranslatingTypeLoader implements ITypeLoader { return classNamesToTry; } - public CtClass transformClass(CtClass c) - throws IOException, NotFoundException, CannotCompileException { - - // reconstruct inner classes - InnerClassWriter.write(jarIndex, c); - - // re-get the javassist handle since we changed class names - ClassEntry obfClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - String javaClassReconstructedName = Descriptor.toJavaName(obfClassEntry.getName()); - ClassPool classPool = new ClassPool(); - classPool.insertClassPath(new ByteArrayClassPath(javaClassReconstructedName, c.toBytecode())); - c = classPool.get(javaClassReconstructedName); - - // check that the file is correct after inner class reconstruction (ie cause Javassist to fail fast if something is wrong) - assertClassName(c, obfClassEntry); - - // do all kinds of deobfuscating transformations on the class - BridgeMarker.markBridges(this.jarIndex, c); - MethodParameterTranslator.translate(this.deobfuscatingTranslator, c); - LocalVariableTranslator.translate(this.deobfuscatingTranslator, c); - ClassTranslator.translate(this.deobfuscatingTranslator, c); - - return c; - } - - private void assertClassName(CtClass c, ClassEntry obfClassEntry) { - String name1 = Descriptor.toJvmName(c.getName()); - assert (name1.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name1); - - String name2 = Descriptor.toJvmName(c.getClassFile().getName()); - assert (name2.equals(obfClassEntry.getName())) : String.format("Looking for %s, instead found %s", obfClassEntry.getName(), name2); + public void createTransformer(ClassNode node, ClassWriter writer) { + node.accept(new TranslationClassVisitor(deobfuscatingTranslator, jarIndex, entryPool, Opcodes.ASM5, writer)); } } diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java index 547d85e..8181418 100644 --- a/src/main/java/cuchaz/enigma/analysis/Access.java +++ b/src/main/java/cuchaz/enigma/analysis/Access.java @@ -11,8 +11,7 @@ package cuchaz.enigma.analysis; -import javassist.CtBehavior; -import javassist.CtField; +import cuchaz.enigma.bytecode.AccessFlags; import java.lang.reflect.Modifier; @@ -20,12 +19,8 @@ public enum Access { PUBLIC, PROTECTED, PACKAGE, PRIVATE; - public static Access get(CtBehavior behavior) { - return get(behavior.getModifiers()); - } - - public static Access get(CtField field) { - return get(field.getModifiers()); + public static Access get(AccessFlags flags) { + return get(flags.getFlags()); } public static Access get(int modifiers) { diff --git a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java deleted file mode 100644 index 6556b2c..0000000 --- a/src/main/java/cuchaz/enigma/analysis/BehaviorReferenceTreeNode.java +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************* - * 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; - -import com.google.common.collect.Sets; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.Entry; -import cuchaz.enigma.mapping.Translator; - -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; -import java.util.Set; - -public class BehaviorReferenceTreeNode extends DefaultMutableTreeNode - implements ReferenceTreeNode { - - private Translator deobfuscatingTranslator; - private BehaviorEntry entry; - private EntryReference reference; - private Access access; - - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, BehaviorEntry entry) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = entry; - this.reference = null; - } - - public BehaviorReferenceTreeNode(Translator deobfuscatingTranslator, - EntryReference reference, Access access) { - this.deobfuscatingTranslator = deobfuscatingTranslator; - this.entry = reference.entry; - this.reference = reference; - this.access = access; - } - - @Override - public BehaviorEntry getEntry() { - return this.entry; - } - - @Override - public EntryReference getReference() { - return this.reference; - } - - @Override - public String toString() { - if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), - this.access); - } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); - } - - public void load(JarIndex index, boolean recurse) { - // get all the child nodes - for (EntryReference reference : index.getBehaviorReferences(this.entry)) { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); - } - - if (recurse && this.children != null) { - for (Object child : this.children) { - if (child instanceof BehaviorReferenceTreeNode) { - BehaviorReferenceTreeNode node = (BehaviorReferenceTreeNode) child; - - // don't recurse into ancestor - Set ancestors = Sets.newHashSet(); - TreeNode n = node; - while (n.getParent() != null) { - n = n.getParent(); - if (n instanceof BehaviorReferenceTreeNode) { - ancestors.add(((BehaviorReferenceTreeNode) n).getEntry()); - } - } - if (ancestors.contains(node.getEntry())) { - continue; - } - - node.load(index, true); - } - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java b/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java deleted file mode 100644 index a2f1f90..0000000 --- a/src/main/java/cuchaz/enigma/analysis/BridgeMarker.java +++ /dev/null @@ -1,38 +0,0 @@ -/******************************************************************************* - * 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; - -import cuchaz.enigma.mapping.EntryFactory; -import cuchaz.enigma.mapping.MethodEntry; -import javassist.CtClass; -import javassist.CtMethod; -import javassist.bytecode.AccessFlag; - -public class BridgeMarker { - - public static void markBridges(JarIndex jarIndex, CtClass c) { - - for (CtMethod method : c.getDeclaredMethods()) { - MethodEntry methodEntry = EntryFactory.getMethodEntry(method); - - // is this a bridge method? - MethodEntry bridgedMethodEntry = jarIndex.getBridgedMethod(methodEntry); - if (bridgedMethodEntry != null) { - - // it's a bridge method! add the bridge flag - int flags = method.getMethodInfo().getAccessFlags(); - flags |= AccessFlag.BRIDGE; - method.getMethodInfo().setAccessFlags(flags); - } - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java index f2fb2f8..d0e1ddb 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java @@ -21,8 +21,8 @@ import java.util.List; public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private ClassEntry entry; + private final Translator deobfuscatingTranslator; + private final ClassEntry entry; public ClassImplementationsTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) { this.deobfuscatingTranslator = deobfuscatingTranslator; @@ -31,7 +31,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) { // is this the node? - if (node.entry.equals(entry.getClassEntry())) { + if (node.entry.equals(entry.getOwnerClassEntry())) { return node; } @@ -50,7 +50,7 @@ public class ClassImplementationsTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(entry).getClassName(); } @Override diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java index 24e7cb0..8fd71b7 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java @@ -20,12 +20,12 @@ import java.util.List; public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { - private Translator deobfuscatingTranslator; - private String obfClassName; + private final Translator deobfuscatingTranslator; + private final ClassEntry obfClassEntry; public ClassInheritanceTreeNode(Translator deobfuscatingTranslator, String obfClassName) { this.deobfuscatingTranslator = deobfuscatingTranslator; - this.obfClassName = obfClassName; + this.obfClassEntry = new ClassEntry(obfClassName); } public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) { @@ -45,11 +45,11 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { } public String getObfClassName() { - return this.obfClassName; + return this.obfClassEntry.getClassName(); } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.obfClassName); + return this.deobfuscatingTranslator.getTranslatedClass(this.obfClassEntry).getClassName(); } @Override @@ -58,13 +58,13 @@ public class ClassInheritanceTreeNode extends DefaultMutableTreeNode { if (deobfClassName != null) { return deobfClassName; } - return this.obfClassName; + return this.obfClassEntry.getName(); } public void load(TranslationIndex ancestries, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : ancestries.getSubclass(new ClassEntry(this.obfClassName))) { + for (ClassEntry subclassEntry : ancestries.getSubclass(this.obfClassEntry)) { nodes.add(new ClassInheritanceTreeNode(this.deobfuscatingTranslator, subclassEntry.getName())); } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java index 3761fca..b972585 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryReference.java @@ -12,8 +12,8 @@ package cuchaz.enigma.analysis; import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ConstructorEntry; import cuchaz.enigma.mapping.Entry; +import cuchaz.enigma.mapping.MethodEntry; import cuchaz.enigma.utils.Utils; import java.util.Arrays; @@ -21,7 +21,7 @@ import java.util.List; public class EntryReference { - private static final List ConstructorNonNames = Arrays.asList("this", "super", "static"); + private static final List CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static"); public E entry; public C context; @@ -40,7 +40,7 @@ public class EntryReference { this.context = context; this.sourceName = sourceName != null && !sourceName.isEmpty(); - if (entry instanceof ConstructorEntry && ConstructorNonNames.contains(sourceName)) { + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) { this.sourceName = false; } } @@ -53,9 +53,9 @@ public class EntryReference { public ClassEntry getLocationClassEntry() { if (context != null) { - return context.getClassEntry(); + return context.getOwnerClassEntry(); } - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public boolean isNamed() { @@ -63,9 +63,9 @@ public class EntryReference { } public Entry getNameableEntry() { - if (entry instanceof ConstructorEntry) { + if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) { // renaming a constructor really means renaming the class - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } return entry; } diff --git a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java index 75806c3..b0bcc91 100644 --- a/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java +++ b/src/main/java/cuchaz/enigma/analysis/EntryRenamer.java @@ -87,18 +87,18 @@ public class EntryRenamer { MethodEntry newMethodEntry = renames.get(methodEntry); if (newMethodEntry != null) { return (T) new MethodEntry( - methodEntry.getClassEntry(), - newMethodEntry.getName(), - methodEntry.getSignature() + methodEntry.getOwnerClassEntry(), + newMethodEntry.getName(), + methodEntry.getDesc() ); } return thing; - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry) thing; - return (T) new ArgumentEntry( - renameMethodsInThing(renames, argumentEntry.getBehaviorEntry()), - argumentEntry.getIndex(), - argumentEntry.getName() + } else if (thing instanceof LocalVariableEntry) { + LocalVariableEntry variableEntry = (LocalVariableEntry) thing; + return (T) new LocalVariableEntry( + renameMethodsInThing(renames, variableEntry.getOwnerEntry()), + variableEntry.getIndex(), + variableEntry.getName() ); } else if (thing instanceof EntryReference) { EntryReference reference = (EntryReference) thing; @@ -119,27 +119,24 @@ public class EntryRenamer { } else if (thing instanceof ClassEntry) { ClassEntry classEntry = (ClassEntry) thing; return (T) new ClassEntry(renameClassesInThing(renames, classEntry.getClassName())); - } else if (thing instanceof FieldEntry) { - FieldEntry fieldEntry = (FieldEntry) thing; - return (T) new FieldEntry(renameClassesInThing(renames, fieldEntry.getClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getType())); - } else if (thing instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) thing; - return (T) new ConstructorEntry(renameClassesInThing(renames, constructorEntry.getClassEntry()), renameClassesInThing(renames, constructorEntry.getSignature())); - } else if (thing instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) thing; - return (T) new MethodEntry(renameClassesInThing(renames, methodEntry.getClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getSignature())); - } else if (thing instanceof ArgumentEntry) { - ArgumentEntry argumentEntry = (ArgumentEntry) thing; - return (T) new ArgumentEntry(renameClassesInThing(renames, argumentEntry.getBehaviorEntry()), argumentEntry.getIndex(), argumentEntry.getName()); + } else if (thing instanceof FieldDefEntry) { + FieldDefEntry fieldEntry = (FieldDefEntry) thing; + return (T) new FieldDefEntry(renameClassesInThing(renames, fieldEntry.getOwnerClassEntry()), fieldEntry.getName(), renameClassesInThing(renames, fieldEntry.getDesc()), fieldEntry.getAccess()); + } else if (thing instanceof MethodDefEntry) { + MethodDefEntry methodEntry = (MethodDefEntry) thing; + return (T) new MethodDefEntry(renameClassesInThing(renames, methodEntry.getOwnerClassEntry()), methodEntry.getName(), renameClassesInThing(renames, methodEntry.getDesc()), methodEntry.getAccess()); + } else if (thing instanceof LocalVariableEntry) { + LocalVariableEntry argumentEntry = (LocalVariableEntry) thing; + return (T) new LocalVariableEntry(renameClassesInThing(renames, argumentEntry.getOwnerEntry()), argumentEntry.getIndex(), argumentEntry.getName()); } else if (thing instanceof EntryReference) { EntryReference reference = (EntryReference) thing; reference.entry = renameClassesInThing(renames, reference.entry); reference.context = renameClassesInThing(renames, reference.context); return thing; - } else if (thing instanceof Signature) { - return (T) new Signature((Signature) thing, className -> renameClassesInThing(renames, className)); - } else if (thing instanceof Type) { - return (T) new Type((Type) thing, className -> renameClassesInThing(renames, className)); + } else if (thing instanceof MethodDescriptor) { + return (T) ((MethodDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); + } else if (thing instanceof TypeDescriptor) { + return (T) ((TypeDescriptor) thing).remap(className -> renameClassesInThing(renames, className)); } return thing; diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java index 34d2eff..3e467db 100644 --- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java @@ -11,17 +11,15 @@ package cuchaz.enigma.analysis; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.Translator; +import cuchaz.enigma.mapping.*; import javax.swing.tree.DefaultMutableTreeNode; -public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { +public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode { private Translator deobfuscatingTranslator; private FieldEntry entry; - private EntryReference reference; + private EntryReference reference; private Access access; public FieldReferenceTreeNode(Translator deobfuscatingTranslator, FieldEntry entry) { @@ -30,7 +28,7 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re this.reference = null; } - private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { + private FieldReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference reference, Access access) { this.deobfuscatingTranslator = deobfuscatingTranslator; this.entry = reference.entry; this.reference = reference; @@ -43,34 +41,34 @@ public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements Re } @Override - public EntryReference getReference() { + public EntryReference getReference() { return this.reference; } @Override public String toString() { if (this.reference != null) { - return String.format("%s (%s)", this.deobfuscatingTranslator.translateEntry(this.reference.context), this.access); + return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), this.access); } - return this.deobfuscatingTranslator.translateEntry(this.entry).toString(); + return deobfuscatingTranslator.getTranslatedField(entry).getName(); } public void load(JarIndex index, boolean recurse) { // get all the child nodes if (this.reference == null) { - for (EntryReference reference : index.getFieldReferences(this.entry)) { + for (EntryReference reference : index.getFieldReferences(this.entry)) { add(new FieldReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); } } else { - for (EntryReference reference : index.getBehaviorReferences(this.reference.context)) { - add(new BehaviorReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); + for (EntryReference reference : index.getMethodReferences(this.reference.context)) { + add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.reference.context))); } } if (recurse && children != null) { for (Object node : children) { - if (node instanceof BehaviorReferenceTreeNode) { - ((BehaviorReferenceTreeNode) node).load(index, true); + if (node instanceof MethodReferenceTreeNode) { + ((MethodReferenceTreeNode) node).load(index, true); } else if (node instanceof FieldReferenceTreeNode) { ((FieldReferenceTreeNode) node).load(index, true); } diff --git a/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java new file mode 100644 index 0000000..97d6ffa --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexClassVisitor.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.ClassDefEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +public class IndexClassVisitor extends ClassVisitor { + private final JarIndex index; + private ClassDefEntry classEntry; + + public IndexClassVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.classEntry = this.index.indexClass(access, name, superName, interfaces); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + if (this.classEntry != null) { + this.index.indexField(this.classEntry, access, name, desc); + } + return super.visitField(access, name, desc, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + if (this.classEntry != null) { + this.index.indexMethod(this.classEntry, access, name, desc); + } + return super.visitMethod(access, name, desc, signature, exceptions); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java new file mode 100644 index 0000000..621bd33 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexInnerClassVisitor.java @@ -0,0 +1,23 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.mapping.ClassEntry; +import org.objectweb.asm.ClassVisitor; + +public class IndexInnerClassVisitor extends ClassVisitor { + private final JarIndex index; + + public IndexInnerClassVisitor(JarIndex index, int api) { + super(api); + this.index = index; + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + ClassEntry entry = new ClassEntry(name); + // Ignore anonymous classes + if (innerName != null && outerName != null) { + ClassEntry outerEntry = new ClassEntry(outerName); + index.indexInnerClass(entry, outerEntry); + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java new file mode 100644 index 0000000..552601b --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/IndexReferenceVisitor.java @@ -0,0 +1,50 @@ +package cuchaz.enigma.analysis; + +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.*; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; + +public class IndexReferenceVisitor extends ClassVisitor { + private final JarIndex index; + private final ReferencedEntryPool entryPool; + private ClassEntry classEntry; + + public IndexReferenceVisitor(JarIndex index, ReferencedEntryPool entryPool, int api) { + super(api); + this.index = index; + this.entryPool = entryPool; + } + + @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), new AccessFlags(access)); + return new Method(this.index, entry, this.api); + } + + private class Method extends MethodVisitor { + private final JarIndex index; + private final MethodDefEntry callerEntry; + + public Method(JarIndex index, MethodDefEntry callerEntry, int api) { + super(api); + this.index = index; + this.callerEntry = callerEntry; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + this.index.indexFieldAccess(callerEntry, owner, name, desc); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + this.index.indexMethodCall(callerEntry, owner, name, desc); + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java b/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java deleted file mode 100644 index 87d3797..0000000 --- a/src/main/java/cuchaz/enigma/analysis/JarClassIterator.java +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * 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; - -import com.google.common.collect.Lists; -import cuchaz.enigma.Constants; -import cuchaz.enigma.mapping.ClassEntry; -import javassist.ByteArrayClassPath; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.NotFoundException; -import javassist.bytecode.Descriptor; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; - -public class JarClassIterator implements Iterator { - - private JarFile jar; - private Iterator iter; - - public JarClassIterator(JarFile jar) { - this.jar = jar; - - // get the jar entries that correspond to classes - List classEntries = Lists.newArrayList(); - Enumeration entries = this.jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (entry.getName().endsWith(".class")) { - classEntries.add(entry); - } - } - this.iter = classEntries.iterator(); - } - - public static List getClassEntries(JarFile jar) { - List classEntries = Lists.newArrayList(); - Enumeration entries = jar.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - - // is this a class file? - if (!entry.isDirectory() && entry.getName().endsWith(".class")) { - classEntries.add(getClassEntry(entry)); - } - } - return classEntries; - } - - public static Iterable classes(final JarFile jar) { - return () -> new JarClassIterator(jar); - } - - private static CtClass getClass(JarFile jar, JarEntry entry) throws IOException, NotFoundException { - // read the class into a buffer - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - byte[] buf = new byte[Constants.KiB]; - int totalNumBytesRead = 0; - InputStream in = jar.getInputStream(entry); - while (in.available() > 0) { - int numBytesRead = in.read(buf); - if (numBytesRead < 0) { - break; - } - bos.write(buf, 0, numBytesRead); - - // sanity checking - totalNumBytesRead += numBytesRead; - if (totalNumBytesRead > Constants.MiB) { - throw new Error("Class file " + entry.getName() + " larger than 1 MiB! Something is wrong!"); - } - } - - // get a javassist handle for the class - String className = Descriptor.toJavaName(getClassEntry(entry).getName()); - ClassPool classPool = new ClassPool(); - classPool.appendSystemPath(); - classPool.insertClassPath(new ByteArrayClassPath(className, bos.toByteArray())); - return classPool.get(className); - } - - private static ClassEntry getClassEntry(JarEntry entry) { - return new ClassEntry(entry.getName().substring(0, entry.getName().length() - ".class".length())); - } - - @Override - public boolean hasNext() { - return this.iter.hasNext(); - } - - @Override - public CtClass next() { - JarEntry entry = this.iter.next(); - try { - return getClass(this.jar, entry); - } catch (IOException | NotFoundException ex) { - throw new Error("Unable to load class: " + entry.getName()); - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/JarIndex.java index d0d0f2c..972d4fe 100644 --- a/src/main/java/cuchaz/enigma/analysis/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/JarIndex.java @@ -12,113 +12,68 @@ package cuchaz.enigma.analysis; import com.google.common.collect.*; +import cuchaz.enigma.bytecode.AccessFlags; import cuchaz.enigma.mapping.*; -import cuchaz.enigma.mapping.Translator; -import javassist.*; -import javassist.bytecode.*; -import javassist.expr.*; +import org.objectweb.asm.Opcodes; -import java.lang.reflect.Modifier; import java.util.*; -import java.util.jar.JarFile; public class JarIndex { + private final ReferencedEntryPool entryPool; + private Set obfClassEntries; private TranslationIndex translationIndex; private Map access; - private Multimap fields; - private Multimap behaviors; - private Multimap methodImplementations; - private Multimap> behaviorReferences; - private Multimap> fieldReferences; + private Multimap fields; + private Multimap methods; + private Multimap methodImplementations; + private Multimap> methodReferences; + private Multimap> fieldReferences; private Multimap innerClassesByOuter; private Map outerClassesByInner; - private Map anonymousClasses; private Map bridgedMethods; private Set syntheticMethods; - public JarIndex() { + public JarIndex(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; this.obfClassEntries = Sets.newHashSet(); - this.translationIndex = new TranslationIndex(); + this.translationIndex = new TranslationIndex(entryPool); this.access = Maps.newHashMap(); this.fields = HashMultimap.create(); - this.behaviors = HashMultimap.create(); + this.methods = HashMultimap.create(); this.methodImplementations = HashMultimap.create(); - this.behaviorReferences = HashMultimap.create(); + this.methodReferences = HashMultimap.create(); this.fieldReferences = HashMultimap.create(); this.innerClassesByOuter = HashMultimap.create(); this.outerClassesByInner = Maps.newHashMap(); - this.anonymousClasses = Maps.newHashMap(); this.bridgedMethods = Maps.newHashMap(); this.syntheticMethods = Sets.newHashSet(); } - public void indexJar(JarFile jar, boolean buildInnerClasses) { + public void indexJar(ParsedJar jar, boolean buildInnerClasses) { // step 1: read the class names - this.obfClassEntries.addAll(JarClassIterator.getClassEntries(jar)); - - // step 2: index field/method/constructor access - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.access.put(fieldEntry, Access.get(field)); - this.fields.put(fieldEntry.getClassEntry(), fieldEntry); - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.access.put(behaviorEntry, Access.get(behavior)); - this.behaviors.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } + obfClassEntries.addAll(jar.getClassEntries()); - // step 3: index extends, implements, fields, and methods - for (CtClass c : JarClassIterator.classes(jar)) { - this.translationIndex.indexClass(c); - String className = Descriptor.toJvmName(c.getName()); - for (String interfaceName : c.getClassFile().getInterfaces()) { - className = Descriptor.toJvmName(className); - interfaceName = Descriptor.toJvmName(interfaceName); - if (className.equals(interfaceName)) { - throw new IllegalArgumentException("Class cannot be its own interface! " + className); - } - } - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehavior(behavior); - } - } + // step 2: index classes, fields, methods, interfaces + jar.visit(node -> node.accept(new IndexClassVisitor(this, Opcodes.ASM5))); - // step 4: index field, method, constructor references - for (CtClass c : JarClassIterator.classes(jar)) { - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - indexBehaviorReferences(behavior); + // step 3: index field, method, constructor references + jar.visit(node -> node.accept(new IndexReferenceVisitor(this, entryPool, Opcodes.ASM5))); + + // step 4: index bridged methods + for (MethodDefEntry methodEntry : methods.values()) { + // look for bridge and bridged methods + MethodEntry bridgedMethod = findBridgedMethod(methodEntry); + if (bridgedMethod != null) { + this.bridgedMethods.put(methodEntry, bridgedMethod); } } if (buildInnerClasses) { - // step 5: index inner classes and anonymous classes - for (CtClass c : JarClassIterator.classes(jar)) { - ClassEntry innerClassEntry = EntryFactory.getClassEntry(c); - ClassEntry outerClassEntry = findOuterClass(c); - if (outerClassEntry != null) { - this.innerClassesByOuter.put(outerClassEntry, innerClassEntry); - boolean innerWasAdded = this.outerClassesByInner.put(innerClassEntry, outerClassEntry) == null; - assert (innerWasAdded); - - BehaviorEntry enclosingBehavior = isAnonymousClass(c, outerClassEntry); - if (enclosingBehavior != null) { - this.anonymousClasses.put(innerClassEntry, enclosingBehavior); - - // DEBUG - //System.out.println("ANONYMOUS: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); - }/* else { - // DEBUG - //System.out.println("INNER: " + outerClassEntry.getName() + "$" + innerClassEntry.getSimpleName()); - }*/ - } - } + jar.visit(node -> node.accept(new IndexInnerClassVisitor(this, Opcodes.ASM5))); // step 6: update other indices with inner class info Map renames = Maps.newHashMap(); @@ -133,385 +88,109 @@ public class JarIndex { EntryRenamer.renameClassesInSet(renames, this.obfClassEntries); this.translationIndex.renameClasses(renames); EntryRenamer.renameClassesInMultimap(renames, this.methodImplementations); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorReferences); + EntryRenamer.renameClassesInMultimap(renames, this.methodReferences); EntryRenamer.renameClassesInMultimap(renames, this.fieldReferences); EntryRenamer.renameClassesInMap(renames, this.access); } } - private void indexBehavior(CtBehavior behavior) { - // get the behavior entry - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - if (behaviorEntry instanceof MethodEntry) { - MethodEntry methodEntry = (MethodEntry) behaviorEntry; - - // is synthetic - if ((behavior.getModifiers() & AccessFlag.SYNTHETIC) != 0) { - syntheticMethods.add(methodEntry); - } - - // index implementation - this.methodImplementations.put(behaviorEntry.getClassName(), methodEntry); - - // look for bridge and bridged methods - CtMethod bridgedMethod = getBridgedMethod((CtMethod) behavior); - if (bridgedMethod != null) { - this.bridgedMethods.put(methodEntry, EntryFactory.getMethodEntry(bridgedMethod)); + protected ClassDefEntry indexClass(int access, String name, String superName, String[] interfaces) { + for (String interfaceName : interfaces) { + if (name.equals(interfaceName)) { + throw new IllegalArgumentException("Class cannot be its own interface! " + name); } } - // looks like we don't care about constructors here - } - - private void indexBehaviorReferences(CtBehavior behavior) { - // index method calls - final BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - try { - behavior.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - MethodEntry calledMethodEntry = EntryFactory.getMethodEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledMethodEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledMethodEntry.getClassEntry())) { - calledMethodEntry = new MethodEntry( - resolvedClassEntry, - calledMethodEntry.getName(), - calledMethodEntry.getSignature() - ); - } - EntryReference reference = new EntryReference<>( - calledMethodEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledMethodEntry, reference); - } - - @Override - public void edit(FieldAccess call) { - FieldEntry calledFieldEntry = EntryFactory.getFieldEntry(call); - ClassEntry resolvedClassEntry = translationIndex.resolveEntryClass(calledFieldEntry); - if (resolvedClassEntry != null && !resolvedClassEntry.equals(calledFieldEntry.getClassEntry())) { - calledFieldEntry = new FieldEntry(calledFieldEntry, resolvedClassEntry); - } - EntryReference reference = new EntryReference<>( - calledFieldEntry, - call.getFieldName(), - behaviorEntry - ); - fieldReferences.put(calledFieldEntry, reference); - } - - @Override - public void edit(ConstructorCall call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getMethodName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - - @Override - public void edit(NewExpr call) { - ConstructorEntry calledConstructorEntry = EntryFactory.getConstructorEntry(call); - EntryReference reference = new EntryReference<>( - calledConstructorEntry, - call.getClassName(), - behaviorEntry - ); - behaviorReferences.put(calledConstructorEntry, reference); - } - }); - } catch (CannotCompileException ex) { - throw new Error(ex); - } + return this.translationIndex.indexClass(access, name, superName, interfaces); } - private CtMethod getBridgedMethod(CtMethod method) { - - // bridge methods just call another method, cast it to the return type, and return the result - // let's see if we can detect this scenario - - // skip non-synthetic methods - if ((method.getModifiers() & AccessFlag.SYNTHETIC) == 0) { - return null; - } - - // get all the called methods - final List methodCalls = Lists.newArrayList(); - try { - method.instrument(new ExprEditor() { - @Override - public void edit(MethodCall call) { - methodCalls.add(call); - } - }); - } catch (CannotCompileException ex) { - // this is stupid... we're not even compiling anything - throw new Error(ex); - } - - // is there just one? - if (methodCalls.size() != 1) { - return null; - } - MethodCall call = methodCalls.get(0); - - try { - // we have a bridge method! - return call.getMethod(); - } catch (NotFoundException ex) { - // can't find the type? not a bridge method - return null; - } + protected void indexField(ClassDefEntry owner, int access, String name, String desc) { + FieldDefEntry fieldEntry = new FieldDefEntry(owner, name, new TypeDescriptor(desc), new AccessFlags(access)); + this.translationIndex.indexField(fieldEntry); + this.access.put(fieldEntry, Access.get(access)); + this.fields.put(fieldEntry.getOwnerClassEntry(), fieldEntry); } - private ClassEntry findOuterClass(CtClass c) { - - ClassEntry classEntry = EntryFactory.getClassEntry(c); + protected void indexMethod(ClassDefEntry owner, int access, String name, String desc) { + MethodDefEntry methodEntry = new MethodDefEntry(owner, name, new MethodDescriptor(desc), new AccessFlags(access)); + this.translationIndex.indexMethod(methodEntry); + this.access.put(methodEntry, Access.get(access)); + this.methods.put(methodEntry.getOwnerClassEntry(), methodEntry); - // does this class already have an outer class? - if (classEntry.isInnerClass()) { - return classEntry.getOuterClassEntry(); + if (new AccessFlags(access).isSynthetic()) { + syntheticMethods.add(methodEntry); } - // inner classes: - // have constructors that can (illegally) set synthetic fields - // the outer class is the only class that calls constructors - - // use the synthetic fields to find the synthetic constructors - for (CtConstructor constructor : c.getDeclaredConstructors()) { - Set syntheticFieldTypes = Sets.newHashSet(); - if (!isIllegalConstructor(syntheticFieldTypes, constructor)) { - continue; - } - - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - - // gather the classes from the illegally-set synthetic fields - Set illegallySetClasses = Sets.newHashSet(); - for (String type : syntheticFieldTypes) { - if (type.startsWith("L")) { - ClassEntry outerClassEntry = new ClassEntry(type.substring(1, type.length() - 1)); - if (isSaneOuterClass(outerClassEntry, classEntry)) { - illegallySetClasses.add(outerClassEntry); - } - } - } - - // who calls this constructor? - Set callerClasses = Sets.newHashSet(); - for (EntryReference reference : getBehaviorReferences(constructorEntry)) { - - // make sure it's not a call to super - if (reference.entry instanceof ConstructorEntry && reference.context instanceof ConstructorEntry) { - - // is the entry a superclass of the context? - ClassEntry calledClassEntry = reference.entry.getClassEntry(); - ClassEntry superclassEntry = this.translationIndex.getSuperclass(reference.context.getClassEntry()); - if (superclassEntry != null && superclassEntry.equals(calledClassEntry)) { - // it's a super call, skip - continue; - } - } - - if (isSaneOuterClass(reference.context.getClassEntry(), classEntry)) { - callerClasses.add(reference.context.getClassEntry()); - } - } - - // do we have an answer yet? - if (callerClasses.isEmpty()) { - if (illegallySetClasses.size() == 1) { - return illegallySetClasses.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to find outer class for %s. No caller and no illegally set field classes.", classEntry)); - } - } else { - if (callerClasses.size() == 1) { - return callerClasses.iterator().next(); - } else { - // multiple callers, do the illegally set classes narrow it down? - Set intersection = Sets.newHashSet(callerClasses); - intersection.retainAll(illegallySetClasses); - if (intersection.size() == 1) { - return intersection.iterator().next(); - } else { - System.out.println(String.format("WARNING: Unable to choose outer class for %s among options: %s", classEntry, callerClasses)); - } - } - } + // we don't care about constructors here + if (!methodEntry.isConstructor()) { + // index implementation + this.methodImplementations.put(methodEntry.getClassName(), methodEntry); } - - return null; } - private boolean isSaneOuterClass(ClassEntry outerClassEntry, ClassEntry innerClassEntry) { - - // clearly this would be silly - if (outerClassEntry.equals(innerClassEntry)) { - return false; + protected void indexMethodCall(MethodDefEntry callerEntry, String owner, String name, String desc) { + MethodEntry referencedMethod = new MethodEntry(entryPool.getClass(owner), name, new MethodDescriptor(desc)); + ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedMethod); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedMethod.getOwnerClassEntry())) { + referencedMethod = referencedMethod.updateOwnership(resolvedClassEntry); } - - // is the outer class in the jar? - return this.obfClassEntries.contains(outerClassEntry); - + methodReferences.put(referencedMethod, new EntryReference<>(referencedMethod, referencedMethod.getName(), callerEntry)); } - @SuppressWarnings("unchecked") - private boolean isIllegalConstructor(Set syntheticFieldTypes, CtConstructor constructor) { - - // illegal constructors only set synthetic member fields, then call super() - String className = constructor.getDeclaringClass().getName(); - - // collect all the field accesses, constructor calls, and method calls - final List illegalFieldWrites = Lists.newArrayList(); - final List constructorCalls = Lists.newArrayList(); - try { - constructor.instrument(new ExprEditor() { - @Override - public void edit(FieldAccess fieldAccess) { - if (fieldAccess.isWriter() && constructorCalls.isEmpty()) { - illegalFieldWrites.add(fieldAccess); - } - } - - @Override - public void edit(ConstructorCall constructorCall) { - constructorCalls.add(constructorCall); - } - }); - } catch (CannotCompileException ex) { - // we're not compiling anything... this is stupid - throw new Error(ex); - } - - // are there any illegal field writes? - if (illegalFieldWrites.isEmpty()) { - return false; - } - - // are all the writes to synthetic fields? - for (FieldAccess fieldWrite : illegalFieldWrites) { - - // all illegal writes have to be to the local class - if (!fieldWrite.getClassName().equals(className)) { - System.err.println(String.format("WARNING: illegal write to non-member field %s.%s", fieldWrite.getClassName(), fieldWrite.getFieldName())); - return false; - } - - // find the field - FieldInfo fieldInfo = null; - for (FieldInfo info : (List) constructor.getDeclaringClass().getClassFile().getFields()) { - if (info.getName().equals(fieldWrite.getFieldName()) && info.getDescriptor().equals(fieldWrite.getSignature())) { - fieldInfo = info; - break; - } - } - if (fieldInfo == null) { - // field is in a superclass or something, can't be a local synthetic member - return false; - } - - // is this field synthetic? - boolean isSynthetic = (fieldInfo.getAccessFlags() & AccessFlag.SYNTHETIC) != 0; - if (isSynthetic) { - syntheticFieldTypes.add(fieldInfo.getDescriptor()); - } else { - System.err.println(String.format("WARNING: illegal write to non synthetic field %s %s.%s", fieldInfo.getDescriptor(), className, fieldInfo.getName())); - return false; - } + protected void indexFieldAccess(MethodDefEntry callerEntry, String owner, String name, String desc) { + FieldEntry referencedField = new FieldEntry(entryPool.getClass(owner), name, new TypeDescriptor(desc)); + ClassEntry resolvedClassEntry = translationIndex.resolveEntryOwner(referencedField); + if (resolvedClassEntry != null && !resolvedClassEntry.equals(referencedField.getOwnerClassEntry())) { + referencedField = referencedField.updateOwnership(resolvedClassEntry); } - - // we passed all the tests! - return true; + fieldReferences.put(referencedField, new EntryReference<>(referencedField, referencedField.getName(), callerEntry)); } - private BehaviorEntry isAnonymousClass(CtClass c, ClassEntry outerClassEntry) { - - // is this class already marked anonymous? - EnclosingMethodAttribute enclosingMethodAttribute = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); - if (enclosingMethodAttribute != null) { - if (enclosingMethodAttribute.methodIndex() > 0) { - return EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(enclosingMethodAttribute.className()), - enclosingMethodAttribute.methodName(), - enclosingMethodAttribute.methodDescriptor() - ); - } else { - // an attribute but no method? assume not anonymous - return null; - } - } - - // if there's an inner class attribute, but not an enclosing method attribute, then it's not anonymous - InnerClassesAttribute innerClassesAttribute = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (innerClassesAttribute != null) { - return null; - } + public void indexInnerClass(ClassEntry innerEntry, ClassEntry outerEntry) { + this.innerClassesByOuter.put(outerEntry, innerEntry); + boolean innerWasAdded = this.outerClassesByInner.put(innerEntry, outerEntry) == null; + assert (innerWasAdded); + } - ClassEntry innerClassEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); + private MethodEntry findBridgedMethod(MethodDefEntry method) { - // anonymous classes: - // can't be abstract - // have only one constructor - // it's called exactly once by the outer class - // the type the instance is assigned to can't be this type + // bridge methods just call another method, cast it to the return desc, and return the result + // let's see if we can detect this scenario - // is abstract? - if (Modifier.isAbstract(c.getModifiers())) { + // skip non-synthetic methods + if (!method.getAccess().isSynthetic()) { return null; } - // is there exactly one constructor? - if (c.getDeclaredConstructors().length != 1) { - return null; - } - CtConstructor constructor = c.getDeclaredConstructors()[0]; + // get all the called methods + final Collection> referencedMethods = methodReferences.get(method); - // is this constructor called exactly once? - ConstructorEntry constructorEntry = EntryFactory.getConstructorEntry(constructor); - Collection> references = getBehaviorReferences(constructorEntry); - if (references.size() != 1) { + // is there just one? + if (referencedMethods.size() != 1) { return null; } - // does the caller use this type? - BehaviorEntry caller = references.iterator().next().context; - for (FieldEntry fieldEntry : getReferencedFields(caller)) { - if (fieldEntry.getType().hasClass() && fieldEntry.getType().getClassEntry().equals(innerClassEntry)) { - // caller references this type, so it can't be anonymous - return null; - } - } - for (BehaviorEntry behaviorEntry : getReferencedBehaviors(caller)) { - if (behaviorEntry.getSignature().hasClass(innerClassEntry)) { - return null; - } - } - - return caller; + // we have a bridge method! + return referencedMethods.stream().findFirst().get().entry; } public Set getObfClassEntries() { return this.obfClassEntries; } - public Collection getObfFieldEntries() { + public Collection getObfFieldEntries() { return this.fields.values(); } - public Collection getObfFieldEntries(ClassEntry classEntry) { + public Collection getObfFieldEntries(ClassEntry classEntry) { return this.fields.get(classEntry); } - public Collection getObfBehaviorEntries() { - return this.behaviors.values(); + public Collection getObfBehaviorEntries() { + return this.methods.values(); } - public Collection getObfBehaviorEntries(ClassEntry classEntry) { - return this.behaviors.get(classEntry); + public Collection getObfBehaviorEntries(ClassEntry classEntry) { + return this.methods.get(classEntry); } public TranslationIndex getTranslationIndex() { @@ -533,8 +212,8 @@ public class JarIndex { } } ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode( - deobfuscatingTranslator, - ancestry.get(ancestry.size() - 1) + deobfuscatingTranslator, + ancestry.get(ancestry.size() - 1) ); // expand all children recursively @@ -557,28 +236,20 @@ public class JarIndex { public MethodInheritanceTreeNode getMethodInheritance(Translator deobfuscatingTranslator, MethodEntry obfMethodEntry) { // travel to the ancestor implementation - ClassEntry baseImplementationClassEntry = obfMethodEntry.getClassEntry(); - for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getClassEntry())) { - MethodEntry ancestorMethodEntry = new MethodEntry( - new ClassEntry(ancestorClassEntry), - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(ancestorMethodEntry)) { + ClassEntry baseImplementationClassEntry = obfMethodEntry.getOwnerClassEntry(); + for (ClassEntry ancestorClassEntry : this.translationIndex.getAncestry(obfMethodEntry.getOwnerClassEntry())) { + MethodEntry ancestorMethodEntry = entryPool.getMethod(ancestorClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); + if (ancestorMethodEntry != null && containsObfMethod(ancestorMethodEntry)) { baseImplementationClassEntry = ancestorClassEntry; } } // make a root node at the base - MethodEntry methodEntry = new MethodEntry( - baseImplementationClassEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); + MethodEntry methodEntry = entryPool.getMethod(baseImplementationClassEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode( - deobfuscatingTranslator, - methodEntry, - containsObfBehavior(methodEntry) + deobfuscatingTranslator, + methodEntry, + containsObfMethod(methodEntry) ); // expand the full tree @@ -599,12 +270,8 @@ public class JarIndex { for (ClassEntry interfaceEntry : getInterfaces(obfMethodEntry.getClassName())) { // is this method defined in this interface? - MethodEntry methodInterface = new MethodEntry( - interfaceEntry, - obfMethodEntry.getName(), - obfMethodEntry.getSignature() - ); - if (containsObfBehavior(methodInterface)) { + MethodEntry methodInterface = entryPool.getMethod(interfaceEntry, obfMethodEntry.getName(), obfMethodEntry.getDesc().toString()); + if (methodInterface != null && containsObfMethod(methodInterface)) { interfaceMethodEntries.add(methodInterface); } } @@ -623,14 +290,14 @@ public class JarIndex { public Set getRelatedMethodImplementations(MethodEntry obfMethodEntry) { Set methodEntries = Sets.newHashSet(); - getRelatedMethodImplementations(methodEntries, getMethodInheritance(new Translator(), obfMethodEntry)); + getRelatedMethodImplementations(methodEntries, getMethodInheritance(new DirectionalTranslator(entryPool), obfMethodEntry)); return methodEntries; } private void getRelatedMethodImplementations(Set methodEntries, MethodInheritanceTreeNode node) { MethodEntry methodEntry = node.getMethodEntry(); - if (containsObfBehavior(methodEntry)) { + if (containsObfMethod(methodEntry)) { // collect the entry methodEntries.add(methodEntry); } @@ -643,7 +310,7 @@ public class JarIndex { } // look at interface methods too - for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new Translator(), methodEntry)) { + for (MethodImplementationsTreeNode implementationsNode : getMethodImplementations(new DirectionalTranslator(entryPool), methodEntry)) { getRelatedMethodImplementations(methodEntries, implementationsNode); } @@ -655,7 +322,7 @@ public class JarIndex { private void getRelatedMethodImplementations(Set methodEntries, MethodImplementationsTreeNode node) { MethodEntry methodEntry = node.getMethodEntry(); - if (containsObfBehavior(methodEntry)) { + if (containsObfMethod(methodEntry)) { // collect the entry methodEntries.add(methodEntry); } @@ -673,30 +340,30 @@ public class JarIndex { } } - public Collection> getFieldReferences(FieldEntry fieldEntry) { + public Collection> getFieldReferences(FieldEntry fieldEntry) { return this.fieldReferences.get(fieldEntry); } - public Collection getReferencedFields(BehaviorEntry behaviorEntry) { + public Collection getReferencedFields(MethodDefEntry methodEntry) { // linear search is fast enough for now Set fieldEntries = Sets.newHashSet(); - for (EntryReference reference : this.fieldReferences.values()) { - if (reference.context == behaviorEntry) { + for (EntryReference reference : this.fieldReferences.values()) { + if (reference.context == methodEntry) { fieldEntries.add(reference.entry); } } return fieldEntries; } - public Collection> getBehaviorReferences(BehaviorEntry behaviorEntry) { - return this.behaviorReferences.get(behaviorEntry); + public Collection> getMethodReferences(MethodEntry methodEntry) { + return this.methodReferences.get(methodEntry); } - public Collection getReferencedBehaviors(BehaviorEntry behaviorEntry) { + public Collection getReferencedMethods(MethodDefEntry methodEntry) { // linear search is fast enough for now - Set behaviorEntries = Sets.newHashSet(); - for (EntryReference reference : this.behaviorReferences.values()) { - if (reference.context == behaviorEntry) { + Set behaviorEntries = Sets.newHashSet(); + for (EntryReference reference : this.methodReferences.values()) { + if (reference.context == methodEntry) { behaviorEntries.add(reference.entry); } } @@ -711,20 +378,12 @@ public class JarIndex { return this.outerClassesByInner.get(obfInnerClassEntry); } - public boolean isAnonymousClass(ClassEntry obfInnerClassEntry) { - return this.anonymousClasses.containsKey(obfInnerClassEntry); - } - public boolean isSyntheticMethod(MethodEntry methodEntry) { return this.syntheticMethods.contains(methodEntry); } - public BehaviorEntry getAnonymousClassCaller(ClassEntry obfInnerClassName) { - return this.anonymousClasses.get(obfInnerClassName); - } - public Set getInterfaces(String className) { - ClassEntry classEntry = new ClassEntry(className); + ClassEntry classEntry = entryPool.getClass(className); Set interfaces = new HashSet<>(); interfaces.addAll(this.translationIndex.getInterfaces(classEntry)); for (ClassEntry ancestor : this.translationIndex.getAncestry(classEntry)) { @@ -754,7 +413,7 @@ public class JarIndex { } public boolean isInterface(String className) { - return this.translationIndex.isInterface(new ClassEntry(className)); + return this.translationIndex.isInterface(entryPool.getClass(className)); } public boolean containsObfClass(ClassEntry obfClassEntry) { @@ -765,8 +424,8 @@ public class JarIndex { return this.access.containsKey(obfFieldEntry); } - public boolean containsObfBehavior(BehaviorEntry obfBehaviorEntry) { - return this.access.containsKey(obfBehaviorEntry); + public boolean containsObfMethod(MethodEntry obfMethodEntry) { + return this.access.containsKey(obfMethodEntry); } public boolean containsEntryWithSameName(Entry entry) { @@ -776,15 +435,13 @@ public class JarIndex { return false; } - public boolean containsObfArgument(ArgumentEntry obfArgumentEntry) { + public boolean containsObfVariable(LocalVariableEntry obfVariableEntry) { // check the behavior - if (!containsObfBehavior(obfArgumentEntry.getBehaviorEntry())) { + if (!containsObfMethod(obfVariableEntry.getOwnerEntry())) { return false; } - // check the argument - return obfArgumentEntry.getIndex() < obfArgumentEntry.getBehaviorEntry().getSignature().getArgumentTypes().size(); - + return true; } public boolean containsObfEntry(Entry obfEntry) { @@ -792,15 +449,12 @@ public class JarIndex { return containsObfClass((ClassEntry) obfEntry); } else if (obfEntry instanceof FieldEntry) { return containsObfField((FieldEntry) obfEntry); - } else if (obfEntry instanceof BehaviorEntry) { - return containsObfBehavior((BehaviorEntry) obfEntry); - } else if (obfEntry instanceof ArgumentEntry) { - return containsObfArgument((ArgumentEntry) obfEntry); + } else if (obfEntry instanceof MethodEntry) { + return containsObfMethod((MethodEntry) obfEntry); } else if (obfEntry instanceof LocalVariableEntry) { - // TODO: Implement it - return false; + return containsObfVariable((LocalVariableEntry) obfEntry); } else { - throw new Error("Entry type not supported: " + obfEntry.getClass().getName()); + throw new Error("Entry desc not supported: " + obfEntry.getClass().getName()); } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java index bacb1aa..6791b83 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java @@ -54,11 +54,11 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getClassName(); } public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); } @Override @@ -80,9 +80,8 @@ public class MethodImplementationsTreeNode extends DefaultMutableTreeNode { // get all method implementations List nodes = Lists.newArrayList(); for (String implementingClassName : index.getImplementingClasses(this.entry.getClassName())) { - MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getSignature() - ); - if (index.containsObfBehavior(methodEntry)) { + MethodEntry methodEntry = new MethodEntry(new ClassEntry(implementingClassName), this.entry.getName(), this.entry.getDesc()); + if (index.containsObfMethod(methodEntry)) { nodes.add(new MethodImplementationsTreeNode(this.deobfuscatingTranslator, methodEntry)); } } diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java index 4f84dd0..f060ed9 100644 --- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java +++ b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java @@ -52,11 +52,11 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { } public String getDeobfClassName() { - return this.deobfuscatingTranslator.translateClass(this.entry.getClassName()); + return this.deobfuscatingTranslator.getTranslatedClass(this.entry.getOwnerClassEntry()).getName(); } public String getDeobfMethodName() { - return this.deobfuscatingTranslator.translate(this.entry); + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); } public boolean isImplemented() { @@ -84,11 +84,9 @@ public class MethodInheritanceTreeNode extends DefaultMutableTreeNode { public void load(JarIndex index, boolean recurse) { // get all the child nodes List nodes = Lists.newArrayList(); - for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getClassEntry())) { - MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getSignature() - ); - nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfBehavior(methodEntry) - )); + for (ClassEntry subclassEntry : index.getTranslationIndex().getSubclass(this.entry.getOwnerClassEntry())) { + MethodEntry methodEntry = new MethodEntry(subclassEntry, this.entry.getName(), this.entry.getDesc()); + nodes.add(new MethodInheritanceTreeNode(this.deobfuscatingTranslator, methodEntry, index.containsObfMethod(methodEntry))); } // add them to this node diff --git a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java new file mode 100644 index 0000000..37b4073 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * 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; + +import com.google.common.collect.Sets; +import cuchaz.enigma.mapping.*; + +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import java.util.Set; + +public class MethodReferenceTreeNode extends DefaultMutableTreeNode + implements ReferenceTreeNode { + + private Translator deobfuscatingTranslator; + private MethodEntry entry; + private EntryReference reference; + private Access access; + + public MethodReferenceTreeNode(Translator deobfuscatingTranslator, MethodEntry entry) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = entry; + this.reference = null; + } + + public MethodReferenceTreeNode(Translator deobfuscatingTranslator, + EntryReference reference, Access access) { + this.deobfuscatingTranslator = deobfuscatingTranslator; + this.entry = reference.entry; + this.reference = reference; + this.access = access; + } + + @Override + public MethodEntry getEntry() { + return this.entry; + } + + @Override + public EntryReference getReference() { + return this.reference; + } + + @Override + public String toString() { + if (this.reference != null) { + return String.format("%s (%s)", this.deobfuscatingTranslator.getTranslatedMethodDef(this.reference.context), + this.access); + } + return this.deobfuscatingTranslator.getTranslatedMethod(this.entry).getName(); + } + + public void load(JarIndex index, boolean recurse) { + // get all the child nodes + for (EntryReference reference : index.getMethodReferences(this.entry)) { + add(new MethodReferenceTreeNode(this.deobfuscatingTranslator, reference, index.getAccess(this.entry))); + } + + if (recurse && this.children != null) { + for (Object child : this.children) { + if (child instanceof MethodReferenceTreeNode) { + MethodReferenceTreeNode node = (MethodReferenceTreeNode) child; + + // don't recurse into ancestor + Set ancestors = Sets.newHashSet(); + TreeNode n = node; + while (n.getParent() != null) { + n = n.getParent(); + if (n instanceof MethodReferenceTreeNode) { + ancestors.add(((MethodReferenceTreeNode) n).getEntry()); + } + } + if (ancestors.contains(node.getEntry())) { + continue; + } + + node.load(index, true); + } + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/ParsedJar.java b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java new file mode 100644 index 0000000..78ef722 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/ParsedJar.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * 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; + +import cuchaz.enigma.mapping.ClassEntry; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import java.util.function.Consumer; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class ParsedJar { + private final Map nodes = new LinkedHashMap<>(); + + public ParsedJar(JarFile jar) throws IOException { + try { + // get the jar entries that correspond to classes + Enumeration entries = jar.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + // is this a class file? + if (entry.getName().endsWith(".class")) { + try (InputStream input = jar.getInputStream(entry)) { + // read the ClassNode from the jar + ClassReader reader = new ClassReader(input); + ClassNode node = new ClassNode(); + reader.accept(node, 0); + String path = entry.getName().substring(0, entry.getName().length() - ".class".length()); + nodes.put(path, node); + } + } + } + } finally { + jar.close(); + } + } + + public void visit(Consumer visitor) { + for (ClassNode node : nodes.values()) { + visitor.accept(node); + } + } + + public int getClassCount() { + return nodes.size(); + } + + public List getClassEntries() { + List entries = new ArrayList<>(nodes.size()); + for (ClassNode node : nodes.values()) { + entries.add(new ClassEntry(node.name)); + } + return entries; + } + + public ClassNode getClassNode(String name) { + return nodes.get(name); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java deleted file mode 100644 index 1b61916..0000000 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexBehaviorVisitor.java +++ /dev/null @@ -1,204 +0,0 @@ -/******************************************************************************* - * 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; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; -import com.strobel.assembler.metadata.MemberReference; -import com.strobel.assembler.metadata.MethodReference; -import com.strobel.assembler.metadata.ParameterDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.languages.TextLocation; -import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.mapping.*; -import javassist.bytecode.Descriptor; - -import java.util.HashMap; -import java.util.Map; - -public class SourceIndexBehaviorVisitor extends SourceIndexVisitor { - private BehaviorEntry behaviorEntry; - - // TODO: Really fix Procyon index problem with inner classes - private int argumentPosition; - private int localsPosition; - private Multimap unmatchedIdentifier = HashMultimap.create(); - private Map identifierEntryCache = new HashMap<>(); - - public SourceIndexBehaviorVisitor(BehaviorEntry behaviorEntry, boolean isEnum) { - this.behaviorEntry = behaviorEntry; - this.argumentPosition = isEnum ? 2 : 0; - this.localsPosition = 0; - } - - @Override - public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - - // get the behavior entry - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - BehaviorEntry behaviorEntry = null; - if (ref instanceof MethodReference) { - MethodReference methodRef = (MethodReference) ref; - if (methodRef.isConstructor()) { - behaviorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - } else if (methodRef.isTypeInitializer()) { - behaviorEntry = new ConstructorEntry(classEntry); - } else { - behaviorEntry = new MethodEntry(classEntry, ref.getName(), new Signature(ref.getErasedSignature())); - } - } - if (behaviorEntry != null) { - // get the node for the token - AstNode tokenNode = null; - if (node.getTarget() instanceof MemberReferenceExpression) { - tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); - } else if (node.getTarget() instanceof SuperReferenceExpression) { - tokenNode = node.getTarget(); - } else if (node.getTarget() instanceof ThisReferenceExpression) { - tokenNode = node.getTarget(); - } - if (tokenNode != null) { - index.addReference(tokenNode, behaviorEntry, this.behaviorEntry); - } - } - - // Check for identifier - node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) - .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); - return recurse(node, index); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - // make sure this is actually a field - if (ref.getErasedSignature().indexOf('(') >= 0) { - throw new Error("Expected a field here! got " + ref); - } - - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getMemberNameToken(), fieldEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitSimpleType(SimpleType node, SourceIndex index) { - TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); - if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { - ClassEntry classEntry = new ClassEntry(ref.getInternalName()); - index.addReference(node.getIdentifierToken(), classEntry, this.behaviorEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { - ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) { - ArgumentEntry argumentEntry = new ArgumentEntry(ProcyonEntryFactory.getBehaviorEntry((MethodReference) def.getMethod()), - argumentPosition++, node.getName()); - Identifier identifier = node.getNameToken(); - // cache the argument entry and the identifier - identifierEntryCache.put(identifier.getName(), argumentEntry); - index.addDeclaration(identifier, argumentEntry); - } - - return recurse(node, index); - } - - @Override - public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new Type(ref.getErasedSignature())); - index.addReference(node.getIdentifierToken(), fieldEntry, this.behaviorEntry); - } else - this.checkIdentifier(node, index); - return recurse(node, index); - } - - private void checkIdentifier(IdentifierExpression node, SourceIndex index) { - if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! - index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); - else - unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! - } - - private void addDeclarationToUnmatched(String key, SourceIndex index) { - Entry entry = identifierEntryCache.get(key); - - // This cannot happened in theory - if (entry == null) - return; - for (Identifier identifier : unmatchedIdentifier.get(key)) - index.addDeclaration(identifier, entry); - unmatchedIdentifier.removeAll(key); - } - - @Override - public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref != null) { - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - ConstructorEntry constructorEntry = new ConstructorEntry(classEntry, new Signature(ref.getErasedSignature())); - if (node.getType() instanceof SimpleType) { - SimpleType simpleTypeNode = (SimpleType) node.getType(); - index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.behaviorEntry); - } - } - - return recurse(node, index); - } - - @Override - public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { - if (node.getVariableType() instanceof SimpleType) { - SimpleType type = (SimpleType) node.getVariableType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - Identifier identifier = node.getVariableNameToken(); - String signature = Descriptor.of(typeReference.getErasedDescription()); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, identifier.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - return recurse(node, index); - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { - AstNodeCollection variables = node.getVariables(); - - // Single assignation - if (variables.size() == 1) { - VariableInitializer initializer = variables.firstOrNullObject(); - if (initializer != null && node.getType() instanceof SimpleType) { - SimpleType type = (SimpleType) node.getType(); - TypeReference typeReference = type.getUserData(Keys.TYPE_REFERENCE); - String signature = Descriptor.of(typeReference.getErasedDescription()); - Identifier identifier = initializer.getNameToken(); - LocalVariableEntry localVariableEntry = new LocalVariableEntry(behaviorEntry, argumentPosition + localsPosition++, initializer.getName(), new Type(signature)); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - } - return recurse(node, index); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java index b13415d..015eaac 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java @@ -20,11 +20,16 @@ import com.strobel.decompiler.languages.java.ast.*; import cuchaz.enigma.mapping.*; public class SourceIndexClassVisitor extends SourceIndexVisitor { + private final ReferencedEntryPool entryPool; + private final ProcyonEntryFactory entryFactory; private ClassEntry classEntry; private boolean isEnum; - public SourceIndexClassVisitor(ClassEntry classEntry) { + public SourceIndexClassVisitor(ReferencedEntryPool entryPool, ClassEntry classEntry) { + super(entryPool); + this.entryPool = entryPool; + this.entryFactory = new ProcyonEntryFactory(entryPool); this.classEntry = classEntry; } @@ -34,9 +39,9 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); ClassEntry classEntry = new ClassEntry(def.getInternalName()); if (!classEntry.equals(this.classEntry)) { - // it's a sub-type, recurse + // it's a subtype, recurse index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); } return recurse(node, index); @@ -56,31 +61,28 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { @Override public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - BehaviorEntry behaviorEntry = ProcyonEntryFactory.getBehaviorEntry(def); + MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); AstNode tokenNode = node.getNameToken(); - if (behaviorEntry instanceof ConstructorEntry) { - ConstructorEntry constructorEntry = (ConstructorEntry) behaviorEntry; - if (constructorEntry.isStatic()) { - // for static initializers, check elsewhere for the token node - tokenNode = node.getModifiers().firstOrNullObject(); - } + if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { + // for static initializers, check elsewhere for the token node + tokenNode = node.getModifiers().firstOrNullObject(); } - index.addDeclaration(tokenNode, behaviorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(behaviorEntry, false), index); + index.addDeclaration(tokenNode, methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry, false), index); } @Override public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - ConstructorEntry constructorEntry = ProcyonEntryFactory.getConstructorEntry(def); - index.addDeclaration(node.getNameToken(), constructorEntry); - return node.acceptVisitor(new SourceIndexBehaviorVisitor(constructorEntry, isEnum), index); + MethodDefEntry methodEntry = entryFactory.getMethodDefEntry(def); + index.addDeclaration(node.getNameToken(), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(entryPool, methodEntry, isEnum), index); } @Override public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def); assert (node.getVariables().size() == 1); VariableInitializer variable = node.getVariables().firstOrNullObject(); index.addDeclaration(variable.getNameToken(), fieldEntry); @@ -92,7 +94,7 @@ public class SourceIndexClassVisitor extends SourceIndexVisitor { public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { // treat enum declarations as field declarations FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldEntry fieldEntry = ProcyonEntryFactory.getFieldEntry(def); + FieldDefEntry fieldEntry = entryFactory.getFieldDefEntry(def); index.addDeclaration(node.getNameToken(), fieldEntry); this.isEnum = true; return recurse(node, index); diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java new file mode 100644 index 0000000..2a62241 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java @@ -0,0 +1,203 @@ +/******************************************************************************* + * 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; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodReference; +import com.strobel.assembler.metadata.ParameterDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.mapping.*; + +import java.util.HashMap; +import java.util.Map; + +public class SourceIndexMethodVisitor extends SourceIndexVisitor { + private final ReferencedEntryPool entryPool; + private final ProcyonEntryFactory entryFactory; + + private MethodDefEntry methodEntry; + + // TODO: Really fix Procyon index problem with inner classes + private int argumentPosition; + private int localsPosition; + private Multimap unmatchedIdentifier = HashMultimap.create(); + private Map identifierEntryCache = new HashMap<>(); + + public SourceIndexMethodVisitor(ReferencedEntryPool entryPool, MethodDefEntry methodEntry, boolean isEnum) { + super(entryPool); + this.entryPool = entryPool; + this.entryFactory = new ProcyonEntryFactory(entryPool); + this.methodEntry = methodEntry; + this.argumentPosition = isEnum ? 2 : 0; + this.localsPosition = 0; + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + // get the behavior entry + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = null; + if (ref instanceof MethodReference) { + methodEntry = entryPool.getMethod(classEntry, ref.getName(), ref.getErasedSignature()); + } + if (methodEntry != null) { + // get the node for the token + AstNode tokenNode = null; + if (node.getTarget() instanceof MemberReferenceExpression) { + tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); + } else if (node.getTarget() instanceof SuperReferenceExpression) { + tokenNode = node.getTarget(); + } else if (node.getTarget() instanceof ThisReferenceExpression) { + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(tokenNode, methodEntry, this.methodEntry); + } + } + + // Check for identifier + node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) + .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); + return recurse(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + // make sure this is actually a field + String erasedSignature = ref.getErasedSignature(); + if (erasedSignature.indexOf('(') >= 0) { + throw new Error("Expected a field here! got " + ref); + } + + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); + if (fieldEntry == null) { + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + } + index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = entryPool.getClass(ref.getInternalName()); + index.addReference(node.getIdentifierToken(), classEntry, this.methodEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + if (def.getMethod() instanceof MemberReference && def.getMethod() instanceof MethodReference) { + MethodEntry methodEntry = entryFactory.getMethodEntry((MethodReference) def.getMethod()); + LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, argumentPosition++, node.getName()); + Identifier identifier = node.getNameToken(); + // cache the argument entry and the identifier + identifierEntryCache.put(identifier.getName(), localVariableEntry); + index.addDeclaration(identifier, localVariableEntry); + } + + return recurse(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = entryPool.getField(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); + if (fieldEntry == null) { + throw new Error("Failed to find field " + ref.getName() + " on " + classEntry.getName()); + } + index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry); + } else + this.checkIdentifier(node, index); + return recurse(node, index); + } + + private void checkIdentifier(IdentifierExpression node, SourceIndex index) { + if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! + index.addDeclaration(node.getIdentifierToken(), identifierEntryCache.get(node.getIdentifier())); + else + unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! + } + + private void addDeclarationToUnmatched(String key, SourceIndex index) { + Entry entry = identifierEntryCache.get(key); + + // This cannot happened in theory + if (entry == null) + return; + for (Identifier identifier : unmatchedIdentifier.get(key)) + index.addDeclaration(identifier, entry); + unmatchedIdentifier.removeAll(key); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = entryPool.getClass(ref.getDeclaringType().getInternalName()); + MethodEntry constructorEntry = entryPool.getMethod(classEntry, "", ref.getErasedSignature()); + if (node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType) node.getType(); + index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry); + } + } + + return recurse(node, index); + } + + @Override + public Void visitForEachStatement(ForEachStatement node, SourceIndex index) { + if (node.getVariableType() instanceof SimpleType) { + Identifier identifier = node.getVariableNameToken(); + LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, argumentPosition + localsPosition++, identifier.getName()); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(identifier, localVariableEntry); + } + return recurse(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + AstNodeCollection variables = node.getVariables(); + + // Single assignation + if (variables.size() == 1) { + VariableInitializer initializer = variables.firstOrNullObject(); + if (initializer != null && node.getType() instanceof SimpleType) { + Identifier identifier = initializer.getNameToken(); + LocalVariableEntry localVariableEntry = new LocalVariableEntry(methodEntry, argumentPosition + localsPosition++, initializer.getName()); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(identifier, localVariableEntry); + } + } + return recurse(node, index); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java index a94a55b..241b9f7 100644 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java @@ -15,8 +15,14 @@ import com.strobel.assembler.metadata.TypeDefinition; import com.strobel.decompiler.languages.java.ast.*; import com.strobel.decompiler.patterns.Pattern; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ReferencedEntryPool; public class SourceIndexVisitor implements IAstVisitor { + private final ReferencedEntryPool entryPool; + + public SourceIndexVisitor(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } @Override public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { @@ -24,7 +30,7 @@ public class SourceIndexVisitor implements IAstVisitor { ClassEntry classEntry = new ClassEntry(def.getInternalName()); index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + return node.acceptVisitor(new SourceIndexClassVisitor(entryPool, classEntry), index); } protected Void recurse(AstNode node, SourceIndex index) { diff --git a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java index 26be05b..3cd80ff 100644 --- a/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/TranslationIndex.java @@ -15,11 +15,8 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; +import cuchaz.enigma.bytecode.AccessFlags; import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.bytecode.Descriptor; import java.util.Collection; import java.util.List; @@ -28,96 +25,91 @@ import java.util.Set; public class TranslationIndex { + private final ReferencedEntryPool entryPool; private Map superclasses; - private Multimap fieldEntries; - private Multimap behaviorEntries; + private Multimap fieldEntries; + private Multimap methodEntries; private Multimap interfaces; - public TranslationIndex() { + public TranslationIndex(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; this.superclasses = Maps.newHashMap(); this.fieldEntries = HashMultimap.create(); - this.behaviorEntries = HashMultimap.create(); + this.methodEntries = HashMultimap.create(); this.interfaces = HashMultimap.create(); } public TranslationIndex(TranslationIndex other, Translator translator) { + this.entryPool = other.entryPool; + // translate the superclasses this.superclasses = Maps.newHashMap(); for (Map.Entry mapEntry : other.superclasses.entrySet()) { - this.superclasses.put(translator.translateEntry(mapEntry.getKey()), translator.translateEntry(mapEntry.getValue())); + this.superclasses.put(translator.getTranslatedClass(mapEntry.getKey()), translator.getTranslatedClass(mapEntry.getValue())); } // translate the interfaces this.interfaces = HashMultimap.create(); for (Map.Entry mapEntry : other.interfaces.entries()) { this.interfaces.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedClass(mapEntry.getValue()) ); } // translate the fields this.fieldEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.fieldEntries.entries()) { + for (Map.Entry mapEntry : other.fieldEntries.entries()) { this.fieldEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedFieldDef(mapEntry.getValue()) ); } - this.behaviorEntries = HashMultimap.create(); - for (Map.Entry mapEntry : other.behaviorEntries.entries()) { - this.behaviorEntries.put( - translator.translateEntry(mapEntry.getKey()), - translator.translateEntry(mapEntry.getValue()) + this.methodEntries = HashMultimap.create(); + for (Map.Entry mapEntry : other.methodEntries.entries()) { + this.methodEntries.put( + translator.getTranslatedClass(mapEntry.getKey()), + translator.getTranslatedMethodDef(mapEntry.getValue()) ); } } - public void indexClass(CtClass c) { - indexClass(c, true); - } - - public void indexClass(CtClass c, boolean indexMembers) { - ClassEntry classEntry = EntryFactory.getClassEntry(c); + protected ClassDefEntry indexClass(int access, String name, String superName, String[] interfaces) { + ClassDefEntry classEntry = new ClassDefEntry(name, new AccessFlags(access)); if (isJre(classEntry)) { - return; + return null; } // add the superclass - ClassEntry superclassEntry = EntryFactory.getSuperclassEntry(c); - if (superclassEntry != null) { + ClassEntry superclassEntry = entryPool.getClass(superName); + if (!isJre(superclassEntry)) { this.superclasses.put(classEntry, superclassEntry); } // add the interfaces - for (String interfaceClassName : c.getClassFile().getInterfaces()) { - ClassEntry interfaceClassEntry = new ClassEntry(Descriptor.toJvmName(interfaceClassName)); + for (String interfaceClassName : interfaces) { + ClassEntry interfaceClassEntry = entryPool.getClass(interfaceClassName); if (!isJre(interfaceClassEntry)) { - this.interfaces.put(classEntry, interfaceClassEntry); } } - if (indexMembers) { - // add fields - for (CtField field : c.getDeclaredFields()) { - FieldEntry fieldEntry = EntryFactory.getFieldEntry(field); - this.fieldEntries.put(fieldEntry.getClassEntry(), fieldEntry); - } + return classEntry; + } - // add behaviors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - this.behaviorEntries.put(behaviorEntry.getClassEntry(), behaviorEntry); - } - } + protected void indexField(FieldDefEntry fieldEntry) { + this.fieldEntries.put(fieldEntry.getOwnerClassEntry(), fieldEntry); + } + + protected void indexMethod(MethodDefEntry methodEntry) { + this.methodEntries.put(methodEntry.getOwnerClassEntry(), methodEntry); } public void renameClasses(Map renames) { EntryRenamer.renameClassesInMap(renames, this.superclasses); EntryRenamer.renameClassesInMultimap(renames, this.fieldEntries); - EntryRenamer.renameClassesInMultimap(renames, this.behaviorEntries); + EntryRenamer.renameClassesInMultimap(renames, this.methodEntries); } public ClassEntry getSuperclass(ClassEntry classEntry) { @@ -175,31 +167,32 @@ public class TranslationIndex { } public boolean entryExists(Entry entry) { + if (entry == null) { + return false; + } if (entry instanceof FieldEntry) { return fieldExists((FieldEntry) entry); - } else if (entry instanceof BehaviorEntry) { - return behaviorExists((BehaviorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return behaviorExists(((ArgumentEntry) entry).getBehaviorEntry()); + } else if (entry instanceof MethodEntry) { + return methodExists((MethodEntry) entry); } else if (entry instanceof LocalVariableEntry) { - return behaviorExists(((LocalVariableEntry) entry).getBehaviorEntry()); + return methodExists(((LocalVariableEntry) entry).getOwnerEntry()); } throw new IllegalArgumentException("Cannot check existence for " + entry.getClass()); } public boolean fieldExists(FieldEntry fieldEntry) { - return this.fieldEntries.containsEntry(fieldEntry.getClassEntry(), fieldEntry); + return this.fieldEntries.containsEntry(fieldEntry.getOwnerClassEntry(), fieldEntry); } - public boolean behaviorExists(BehaviorEntry behaviorEntry) { - return this.behaviorEntries.containsEntry(behaviorEntry.getClassEntry(), behaviorEntry); + public boolean methodExists(MethodEntry methodEntry) { + return this.methodEntries.containsEntry(methodEntry.getOwnerClassEntry(), methodEntry); } - public ClassEntry resolveEntryClass(Entry entry) { - return resolveEntryClass(entry, false); + public ClassEntry resolveEntryOwner(Entry entry) { + return resolveEntryOwner(entry, false); } - public ClassEntry resolveEntryClass(Entry entry, boolean checkSuperclassBeforeChild) { + public ClassEntry resolveEntryOwner(Entry entry, boolean checkSuperclassBeforeChild) { if (entry instanceof ClassEntry) { return (ClassEntry) entry; } @@ -227,12 +220,12 @@ public class TranslationIndex { Entry originalEntry = entry; // Get all possible superclasses and reverse the list - List superclasses = Lists.reverse(getAncestry(originalEntry.getClassEntry())); + List superclasses = Lists.reverse(getAncestry(originalEntry.getOwnerClassEntry())); boolean existInEntry = false; for (ClassEntry classEntry : superclasses) { - entry = entry.cloneToNewClass(classEntry); + entry = entry.updateOwnership(classEntry); existInEntry = entryExists(entry); // Check for possible entry in interfaces of superclasses @@ -245,9 +238,9 @@ public class TranslationIndex { // Doesn't exists in superclasses? check the child or return null if (!existInEntry) - return !entryExists(originalEntry) ? null : originalEntry.getClassEntry(); + return !entryExists(originalEntry) ? null : originalEntry.getOwnerClassEntry(); - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public ClassEntry resolveSuperclass(Entry entry) { @@ -256,7 +249,7 @@ public class TranslationIndex { while (!entryExists(entry)) { // is there a parent class? - ClassEntry superclassEntry = getSuperclass(entry.getClassEntry()); + ClassEntry superclassEntry = getSuperclass(entry.getOwnerClassEntry()); if (superclassEntry == null) { // this is probably a method from a class in a library // we can't trace the implementation up any higher unless we index the library @@ -264,23 +257,23 @@ public class TranslationIndex { } // move up to the parent class - entry = entry.cloneToNewClass(superclassEntry); + entry = entry.updateOwnership(superclassEntry); } - return entry.getClassEntry(); + return entry.getOwnerClassEntry(); } public ClassEntry resolveInterface(Entry entry) { // the interfaces for any class is a forest // so let's look at all the trees - for (ClassEntry interfaceEntry : this.interfaces.get(entry.getClassEntry())) { + for (ClassEntry interfaceEntry : this.interfaces.get(entry.getOwnerClassEntry())) { Collection subInterface = this.interfaces.get(interfaceEntry); if (subInterface != null && !subInterface.isEmpty()) { - ClassEntry result = resolveInterface(entry.cloneToNewClass(interfaceEntry)); + ClassEntry result = resolveInterface(entry.updateOwnership(interfaceEntry)); if (result != null) return result; } - ClassEntry resolvedClassEntry = resolveSuperclass(entry.cloneToNewClass(interfaceEntry)); + ClassEntry resolvedClassEntry = resolveSuperclass(entry.updateOwnership(interfaceEntry)); if (resolvedClassEntry != null) { return resolvedClassEntry; } diff --git a/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java b/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java new file mode 100644 index 0000000..0999abf --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/AccessFlags.java @@ -0,0 +1,79 @@ +package cuchaz.enigma.bytecode; + +import org.objectweb.asm.Opcodes; + +import java.lang.reflect.Modifier; + +public class AccessFlags { + private static final int SYNTHETIC_FLAG = 0x00001000; + private static final int BRIDGED_FLAG = 0x00000040; + + private int flags; + + public AccessFlags(int flags) { + this.flags = flags; + } + + public boolean isPrivate() { + return Modifier.isPrivate(this.flags); + } + + public boolean isProtected() { + return Modifier.isProtected(this.flags); + } + + public boolean isPublic() { + return Modifier.isPublic(this.flags); + } + + public boolean isSynthetic() { + return (this.flags & SYNTHETIC_FLAG) != 0; + } + + public boolean isStatic() { + return Modifier.isStatic(this.flags); + } + + public AccessFlags setPrivate() { + this.setVisibility(Opcodes.ACC_PRIVATE); + return this; + } + + public AccessFlags setProtected() { + this.setVisibility(Opcodes.ACC_PROTECTED); + return this; + } + + public AccessFlags setPublic() { + this.setVisibility(Opcodes.ACC_PUBLIC); + return this; + } + + public AccessFlags setBridged() { + this.setVisibility(BRIDGED_FLAG); + return this; + } + + public void setVisibility(int visibility) { + this.resetVisibility(); + this.flags |= visibility; + } + + private void resetVisibility() { + this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC); + } + + public int getFlags() { + return this.flags; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags; + } + + @Override + public int hashCode() { + return flags; + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java index 6ec576e..9ed6db9 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassProtectifier.java @@ -11,41 +11,39 @@ package cuchaz.enigma.bytecode; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.bytecode.AccessFlag; -import javassist.bytecode.InnerClassesAttribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; -public class ClassProtectifier { +public class ClassProtectifier extends ClassVisitor { - public static CtClass protectify(CtClass c) { - - // protectify all the fields - for (CtField field : c.getDeclaredFields()) { - field.setModifiers(protectify(field.getModifiers())); - } + public ClassProtectifier(int api, ClassVisitor cv) { + super(api, cv); + } - // protectify all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - behavior.setModifiers(protectify(behavior.getModifiers())); - } + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + access = protectify(access); + return super.visitMethod(access, name, desc, signature, exceptions); + } - // protectify all the inner classes - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - attr.setAccessFlags(i, protectify(attr.accessFlags(i))); - } - } + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + access = protectify(access); + return super.visitField(access, name, desc, signature, value); + } - return c; + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + access = protectify(access); + super.visitInnerClass(name, outerName, innerName, access); } - private static int protectify(int flags) { - if (AccessFlag.isPrivate(flags)) { - flags = AccessFlag.setProtected(flags); + private static int protectify(int access) { + AccessFlags accessFlags = new AccessFlags(access); + if (accessFlags.isPrivate()) { + accessFlags.setProtected(); } - return flags; + return accessFlags.getFlags(); } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java index d627fe9..64de788 100644 --- a/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java +++ b/src/main/java/cuchaz/enigma/bytecode/ClassPublifier.java @@ -11,41 +11,45 @@ package cuchaz.enigma.bytecode; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.bytecode.AccessFlag; -import javassist.bytecode.InnerClassesAttribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; -public class ClassPublifier { +public class ClassPublifier extends ClassVisitor { - public static CtClass publify(CtClass c) { + public ClassPublifier(int api, ClassVisitor cv) { + super(api, cv); + } - // publify all the fields - for (CtField field : c.getDeclaredFields()) { - field.setModifiers(publify(field.getModifiers())); - } + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + access = publify(access); + super.visit(version, access, name, signature, superName, interfaces); + } - // publify all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - behavior.setModifiers(publify(behavior.getModifiers())); - } + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + access = publify(access); + return super.visitField(access, name, desc, signature, value); + } - // publify all the inner classes - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - attr.setAccessFlags(i, publify(attr.accessFlags(i))); - } - } + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + access = publify(access); + return super.visitMethod(access, name, desc, signature, exceptions); + } - return c; + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + access = publify(access); + super.visitInnerClass(name, outerName, innerName, access); } - private static int publify(int flags) { - if (!AccessFlag.isPublic(flags)) { - flags = AccessFlag.setPublic(flags); + private static int publify(int access) { + AccessFlags accessFlags = new AccessFlags(access); + if (!accessFlags.isPublic()) { + accessFlags.setPublic(); } - return flags; + return accessFlags.getFlags(); } } diff --git a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java b/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java deleted file mode 100644 index 62a838d..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/ClassRenamer.java +++ /dev/null @@ -1,539 +0,0 @@ -/******************************************************************************* - * 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.bytecode; - -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.Mappings; -import cuchaz.enigma.mapping.Translator; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.Modifier; -import javassist.bytecode.*; -import javassist.bytecode.SignatureAttribute.*; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class ClassRenamer { - - public static void applyModifier(Object obj, Mappings.EntryModifier modifier) { - int mod = -1; - if (obj instanceof CtField) - mod = ((CtField) obj).getModifiers(); - else if (obj instanceof CtBehavior) - mod = ((CtBehavior) obj).getModifiers(); - else if (obj instanceof CtClass) - mod = ((CtClass) obj).getModifiers(); - - if (mod != -1) { - switch (modifier) { - case PRIVATE: - mod = Modifier.setPrivate(mod); - break; - case PROTECTED: - mod = Modifier.setProtected(mod); - break; - case PUBLIC: - mod = Modifier.setPublic(mod); - break; - default: - break; - } - if (obj instanceof CtField) - ((CtField) obj).setModifiers(mod); - else if (obj instanceof CtBehavior) - ((CtBehavior) obj).setModifiers(mod); - else - ((CtClass) obj).setModifiers(mod); - } - } - - public static void renameClasses(CtClass c, final Translator translator) { - renameClasses(c, className -> { - ClassEntry entry = translator.translateEntry(new ClassEntry(className)); - if (entry != null) { - return entry.getName(); - } - return null; - }); - } - - public static void moveAllClassesOutOfDefaultPackage(CtClass c, final String newPackageName) { - renameClasses(c, className -> { - ClassEntry entry = new ClassEntry(className); - if (entry.isInDefaultPackage()) { - return newPackageName + "/" + entry.getName(); - } - return null; - }); - } - - public static void moveAllClassesIntoDefaultPackage(CtClass c, final String oldPackageName) { - renameClasses(c, className -> { - ClassEntry entry = new ClassEntry(className); - if (entry.getPackageName().equals(oldPackageName)) { - return entry.getSimpleName(); - } - return null; - }); - } - - @SuppressWarnings("unchecked") - public static void renameClasses(CtClass c, ClassNameReplacer replacer) { - - // sadly, we can't use CtClass.renameClass() because SignatureAttribute.renameClass() is extremely buggy =( - - ReplacerClassMap map = new ReplacerClassMap(replacer); - ClassFile classFile = c.getClassFile(); - - // rename the constant pool (covers ClassInfo, MethodTypeInfo, and NameAndTypeInfo) - ConstPool constPool = c.getClassFile().getConstPool(); - constPool.renameClass(map); - - // rename class attributes - renameAttributes(classFile.getAttributes(), map, SignatureType.Class); - - // rename methods - for (MethodInfo methodInfo : (List) classFile.getMethods()) { - methodInfo.setDescriptor(Descriptor.rename(methodInfo.getDescriptor(), map)); - renameAttributes(methodInfo.getAttributes(), map, SignatureType.Method); - } - - // rename fields - for (FieldInfo fieldInfo : (List) classFile.getFields()) { - fieldInfo.setDescriptor(Descriptor.rename(fieldInfo.getDescriptor(), map)); - renameAttributes(fieldInfo.getAttributes(), map, SignatureType.Field); - } - - // rename the class name itself last - // NOTE: don't use the map here, because setName() calls the buggy SignatureAttribute.renameClass() - // we only want to replace exactly this class name - String newName = renameClassName(c.getName(), map); - if (newName != null) { - c.setName(newName); - } - - // replace simple names in the InnerClasses attribute too - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) { - for (int i = 0; i < attr.tableLength(); i++) { - - String innerName = attr.innerClass(i); - // get the inner class full name (which has already been translated) - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); - - if (attr.innerNameIndex(i) != 0) { - // update the inner name - attr.setInnerNameIndex(i, constPool.addUtf8Info(classEntry.getInnermostClassName())); - } - - /* DEBUG - System.out.println(String.format("\tDEOBF: %s-> ATTR: %s,%s,%s", classEntry, attr.outerClass(i), attr.innerClass(i), attr.innerName(i))); - */ - } - } - } - - @SuppressWarnings("unchecked") - private static void renameAttributes(List attributes, ReplacerClassMap map, SignatureType type) { - try { - - // make the rename class method accessible - Method renameClassMethod = AttributeInfo.class.getDeclaredMethod("renameClass", Map.class); - renameClassMethod.setAccessible(true); - - for (AttributeInfo attribute : attributes) { - if (attribute instanceof SignatureAttribute) { - // this has to be handled specially because SignatureAttribute.renameClass() is buggy as hell - SignatureAttribute signatureAttribute = (SignatureAttribute) attribute; - String newSignature = type.rename(signatureAttribute.getSignature(), map); - if (newSignature != null) { - signatureAttribute.setSignature(newSignature); - } - } else if (attribute instanceof CodeAttribute) { - // code attributes have signature attributes too (indirectly) - CodeAttribute codeAttribute = (CodeAttribute) attribute; - renameAttributes(codeAttribute.getAttributes(), map, type); - } else if (attribute instanceof LocalVariableTypeAttribute) { - // lvt attributes have signature attributes too - LocalVariableTypeAttribute localVariableAttribute = (LocalVariableTypeAttribute) attribute; - renameLocalVariableTypeAttribute(localVariableAttribute, map); - } else { - renameClassMethod.invoke(attribute, map); - } - } - - } catch (NoSuchMethodException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - throw new Error("Unable to call javassist methods by reflection!", ex); - } - } - - private static void renameLocalVariableTypeAttribute(LocalVariableTypeAttribute attribute, ReplacerClassMap map) { - - // adapted from LocalVariableAttribute.renameClass() - ConstPool cp = attribute.getConstPool(); - int n = attribute.tableLength(); - byte[] info = attribute.get(); - for (int i = 0; i < n; ++i) { - int pos = i * 10 + 2; - int index = ByteArray.readU16bit(info, pos + 6); - if (index != 0) { - String signature = cp.getUtf8Info(index); - String newSignature = renameLocalVariableSignature(signature, map); - if (newSignature != null) { - ByteArray.write16bit(cp.addUtf8Info(newSignature), info, pos + 6); - } - } - } - } - - private static String renameLocalVariableSignature(String signature, ReplacerClassMap map) { - - // for some reason, signatures with . in them don't count as field signatures - // looks like anonymous classes delimit with . in stead of $ - // convert the . to $, but keep track of how many we replace - // we need to put them back after we translate - int start = signature.lastIndexOf('$') + 1; - int numConverted = 0; - StringBuilder buf = new StringBuilder(signature); - for (int i = buf.length() - 1; i >= start; i--) { - char c = buf.charAt(i); - if (c == '.') { - buf.setCharAt(i, '$'); - numConverted++; - } - } - signature = buf.toString(); - - // translate - String newSignature = renameFieldSignature(signature, map); - if (newSignature != null) { - - // put the delimiters back - buf = new StringBuilder(newSignature); - for (int i = buf.length() - 1; i >= 0 && numConverted > 0; i--) { - char c = buf.charAt(i); - if (c == '$') { - buf.setCharAt(i, '.'); - numConverted--; - } - } - assert (numConverted == 0); - newSignature = buf.toString(); - - return newSignature; - } - - return null; - } - - private static String renameClassSignature(String signature, ReplacerClassMap map) { - try { - ClassSignature type = renameType(SignatureAttribute.toClassSignature(signature), map); - return type.encode(); - } catch (BadBytecode ex) { - throw new Error("Can't parse field signature: " + signature); - } - } - - private static String renameFieldSignature(String signature, ReplacerClassMap map) { - try { - ObjectType type = renameType(SignatureAttribute.toFieldSignature(signature), map); - if (type != null) { - return type.encode(); - } - return null; - } catch (BadBytecode ex) { - throw new Error("Can't parse class signature: " + signature); - } - } - - private static String renameMethodSignature(String signature, ReplacerClassMap map) { - try { - MethodSignature type = renameType(SignatureAttribute.toMethodSignature(signature), map); - return type.encode(); - } catch (BadBytecode ex) { - throw new Error("Can't parse method signature: " + signature); - } - } - - private static TypeParameter[] renameTypeParameter(TypeParameter[] typeParamTypes, ReplacerClassMap map) { - if (typeParamTypes != null) { - typeParamTypes = Arrays.copyOf(typeParamTypes, typeParamTypes.length); - for (int i = 0; i < typeParamTypes.length; i++) { - TypeParameter newParamType = renameType(typeParamTypes[i], map); - if (newParamType != null) { - typeParamTypes[i] = newParamType; - } - } - } - return typeParamTypes; - } - - private static ClassSignature renameType(ClassSignature type, ReplacerClassMap map) { - - TypeParameter[] typeParamTypes = renameTypeParameter(type.getParameters(), map); - - ClassType superclassType = type.getSuperClass(); - if (superclassType != ClassType.OBJECT) { - ClassType newSuperclassType = renameType(superclassType, map); - if (newSuperclassType != null) { - superclassType = newSuperclassType; - } - } - - ClassType[] interfaceTypes = type.getInterfaces(); - if (interfaceTypes != null) { - interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); - for (int i = 0; i < interfaceTypes.length; i++) { - ClassType newInterfaceType = renameType(interfaceTypes[i], map); - if (newInterfaceType != null) { - interfaceTypes[i] = newInterfaceType; - } - } - } - - return new ClassSignature(typeParamTypes, superclassType, interfaceTypes); - } - - private static MethodSignature renameType(MethodSignature type, ReplacerClassMap map) { - - TypeParameter[] typeParamTypes = renameTypeParameter(type.getTypeParameters(), map); - - Type[] paramTypes = type.getParameterTypes(); - if (paramTypes != null) { - paramTypes = Arrays.copyOf(paramTypes, paramTypes.length); - for (int i = 0; i < paramTypes.length; i++) { - Type newParamType = renameType(paramTypes[i], map); - if (newParamType != null) { - paramTypes[i] = newParamType; - } - } - } - - Type returnType = type.getReturnType(); - if (returnType != null) { - Type newReturnType = renameType(returnType, map); - if (newReturnType != null) { - returnType = newReturnType; - } - } - - ObjectType[] exceptionTypes = type.getExceptionTypes(); - if (exceptionTypes != null) { - exceptionTypes = Arrays.copyOf(exceptionTypes, exceptionTypes.length); - for (int i = 0; i < exceptionTypes.length; i++) { - ObjectType newExceptionType = renameType(exceptionTypes[i], map); - if (newExceptionType != null) { - exceptionTypes[i] = newExceptionType; - } - } - } - - return new MethodSignature(typeParamTypes, paramTypes, returnType, exceptionTypes); - } - - private static Type renameType(Type type, ReplacerClassMap map) { - if (type instanceof ObjectType) { - return renameType((ObjectType) type, map); - } else if (type instanceof BaseType) { - return renameType((BaseType) type, map); - } else { - throw new Error("Don't know how to rename type " + type.getClass()); - } - } - - private static ObjectType renameType(ObjectType type, ReplacerClassMap map) { - if (type instanceof ArrayType) { - return renameType((ArrayType) type, map); - } else if (type instanceof ClassType) { - return renameType((ClassType) type, map); - } else if (type instanceof TypeVariable) { - return renameType((TypeVariable) type, map); - } else { - throw new Error("Don't know how to rename type " + type.getClass()); - } - } - - private static BaseType renameType(BaseType type, ReplacerClassMap map) { - // don't have to rename primitives - return null; - } - - private static TypeVariable renameType(TypeVariable type, ReplacerClassMap map) { - // don't have to rename template args - return null; - } - - private static ClassType renameType(ClassType type, ReplacerClassMap map) { - - // translate type args - TypeArgument[] args = type.getTypeArguments(); - if (args != null) { - args = Arrays.copyOf(args, args.length); - for (int i = 0; i < args.length; i++) { - TypeArgument newType = renameType(args[i], map); - if (newType != null) { - args[i] = newType; - } - } - } - - if (type instanceof NestedClassType) { - NestedClassType nestedType = (NestedClassType) type; - - // translate the name - String name = getClassName(type); - String newName = map.get(name); - if (newName != null) { - name = new ClassEntry(newName).getInnermostClassName(); - } - - // translate the parent class too - ClassType parent = renameType(nestedType.getDeclaringClass(), map); - if (parent == null) { - parent = nestedType.getDeclaringClass(); - } - - return new NestedClassType(parent, name, args); - } else { - - // translate the name - String name = type.getName(); - String newName = renameClassName(name, map); - if (newName != null) { - name = newName; - } - - return new ClassType(name, args); - } - } - - private static String getClassName(ClassType type) { - if (type instanceof NestedClassType) { - NestedClassType nestedType = (NestedClassType) type; - return getClassName(nestedType.getDeclaringClass()) + "$" + Descriptor.toJvmName(type.getName().replace('.', '$')); - } else { - return Descriptor.toJvmName(type.getName()); - } - } - - private static String renameClassName(String name, ReplacerClassMap map) { - String newName = map.get(Descriptor.toJvmName(name)); - if (newName != null) { - return Descriptor.toJavaName(newName); - } - return null; - } - - private static TypeArgument renameType(TypeArgument type, ReplacerClassMap map) { - ObjectType subType = type.getType(); - if (subType != null) { - ObjectType newSubType = renameType(subType, map); - if (newSubType != null) { - switch (type.getKind()) { - case ' ': - return new TypeArgument(newSubType); - case '+': - return TypeArgument.subclassOf(newSubType); - case '-': - return TypeArgument.superOf(newSubType); - default: - throw new Error("Unknown type kind: " + type.getKind()); - } - } - } - return null; - } - - private static ArrayType renameType(ArrayType type, ReplacerClassMap map) { - Type newSubType = renameType(type.getComponentType(), map); - if (newSubType != null) { - return new ArrayType(type.getDimension(), newSubType); - } - return null; - } - - private static TypeParameter renameType(TypeParameter type, ReplacerClassMap map) { - - ObjectType superclassType = type.getClassBound(); - if (superclassType != null) { - ObjectType newSuperclassType = renameType(superclassType, map); - if (newSuperclassType != null) { - superclassType = newSuperclassType; - } - } - - ObjectType[] interfaceTypes = type.getInterfaceBound(); - if (interfaceTypes != null) { - interfaceTypes = Arrays.copyOf(interfaceTypes, interfaceTypes.length); - for (int i = 0; i < interfaceTypes.length; i++) { - ObjectType newInterfaceType = renameType(interfaceTypes[i], map); - if (newInterfaceType != null) { - interfaceTypes[i] = newInterfaceType; - } - } - } - - return new TypeParameter(type.getName(), superclassType, interfaceTypes); - } - - private enum SignatureType { - Class { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameClassSignature(signature, map); - } - }, - Field { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameFieldSignature(signature, map); - } - }, - Method { - @Override - public String rename(String signature, ReplacerClassMap map) { - return renameMethodSignature(signature, map); - } - }; - - public abstract String rename(String signature, ReplacerClassMap map); - } - - private static class ReplacerClassMap extends HashMap { - - private ClassNameReplacer replacer; - - public ReplacerClassMap(ClassNameReplacer replacer) { - this.replacer = replacer; - } - - @Override - public String get(Object obj) { - if (obj instanceof String) { - return get((String) obj); - } - return null; - } - - public String get(String className) { - return replacer.replace(className); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java b/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java deleted file mode 100644 index 1932730..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/ConstPoolEditor.java +++ /dev/null @@ -1,264 +0,0 @@ -/******************************************************************************* - * 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.bytecode; - -import cuchaz.enigma.bytecode.accessors.ClassInfoAccessor; -import cuchaz.enigma.bytecode.accessors.ConstInfoAccessor; -import cuchaz.enigma.bytecode.accessors.MemberRefInfoAccessor; -import javassist.bytecode.ConstPool; -import javassist.bytecode.Descriptor; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.lang.reflect.Constructor; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.HashMap; - -public class ConstPoolEditor { - - private static Method getItem; - private static Method addItem; - private static Method addItem0; - private static Field items; - private static Field cache; - private static Field numItems; - private static Field objects; - private static Field elements; - private static Method methodWritePool; - private static Constructor constructorPool; - - static { - try { - getItem = ConstPool.class.getDeclaredMethod("getItem", int.class); - getItem.setAccessible(true); - - addItem = ConstPool.class.getDeclaredMethod("addItem", Class.forName("javassist.bytecode.ConstInfo")); - addItem.setAccessible(true); - - addItem0 = ConstPool.class.getDeclaredMethod("addItem0", Class.forName("javassist.bytecode.ConstInfo")); - addItem0.setAccessible(true); - - items = ConstPool.class.getDeclaredField("items"); - items.setAccessible(true); - - cache = ConstPool.class.getDeclaredField("itemsCache"); - cache.setAccessible(true); - - numItems = ConstPool.class.getDeclaredField("numOfItems"); - numItems.setAccessible(true); - - objects = Class.forName("javassist.bytecode.LongVector").getDeclaredField("objects"); - objects.setAccessible(true); - - elements = Class.forName("javassist.bytecode.LongVector").getDeclaredField("elements"); - elements.setAccessible(true); - - methodWritePool = ConstPool.class.getDeclaredMethod("write", DataOutputStream.class); - methodWritePool.setAccessible(true); - - constructorPool = ConstPool.class.getDeclaredConstructor(DataInputStream.class); - constructorPool.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private ConstPool pool; - - public ConstPoolEditor(ConstPool pool) { - this.pool = pool; - } - - public static ConstPool readPool(DataInputStream in) { - try { - return constructorPool.newInstance(in); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static ConstPool newConstPool() { - // const pool expects the name of a class to initialize itself - // but we want an empty pool - // so give it a bogus name, and then clear the entries afterwards - ConstPool pool = new ConstPool("a"); - - ConstPoolEditor editor = new ConstPoolEditor(pool); - int size = pool.getSize(); - for (int i = 0; i < size - 1; i++) { - editor.removeLastItem(); - } - - // make sure the pool is actually empty - // although, in this case "empty" means one thing in it - // the JVM spec says index 0 should be reserved - assert (pool.getSize() == 1); - assert (editor.getItem(0) == null); - assert (editor.getItem(1) == null); - assert (editor.getItem(2) == null); - assert (editor.getItem(3) == null); - - // also, clear the cache - editor.getCache().clear(); - - return pool; - } - - public void writePool(DataOutputStream out) { - try { - methodWritePool.invoke(this.pool, out); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public String getMemberrefClassname(int memberrefIndex) { - return Descriptor.toJvmName(this.pool.getClassInfo(this.pool.getMemberClass(memberrefIndex))); - } - - public String getMemberrefName(int memberrefIndex) { - return this.pool.getUtf8Info(this.pool.getNameAndTypeName(this.pool.getMemberNameAndType(memberrefIndex))); - } - - public String getMemberrefType(int memberrefIndex) { - return this.pool.getUtf8Info(this.pool.getNameAndTypeDescriptor(this.pool.getMemberNameAndType(memberrefIndex))); - } - - public ConstInfoAccessor getItem(int index) { - try { - Object entry = getItem.invoke(this.pool, index); - if (entry == null) { - return null; - } - return new ConstInfoAccessor(entry); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int addItem(Object item) { - try { - return (Integer) addItem.invoke(this.pool, item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int addItemForceNew(Object item) { - try { - return (Integer) addItem0.invoke(this.pool, item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings("rawtypes") - public void removeLastItem() { - try { - // remove the item from the cache - HashMap cache = getCache(); - if (cache != null) { - Object item = getItem(this.pool.getSize() - 1); - cache.remove(item); - } - - // remove the actual item - // based off of LongVector.addElement() - Object item = items.get(this.pool); - Object[][] object = (Object[][]) objects.get(items); - int numElements = (Integer) elements.get(items) - 1; - int nth = numElements >> 7; - int offset = numElements & (128 - 1); - object[nth][offset] = null; - - // decrement the number of items - elements.set(item, numElements); - numItems.set(this.pool, (Integer) numItems.get(this.pool) - 1); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings("rawtypes") - public HashMap getCache() { - try { - return (HashMap) cache.get(this.pool); - } catch (Exception ex) { - throw new Error(ex); - } - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void changeMemberrefNameAndType(int memberrefIndex, String newName, String newType) { - // NOTE: when changing values, we always need to copy-on-write - try { - // get the memberref item - Object item = getItem(memberrefIndex).getItem(); - - // update the cache - HashMap cache = getCache(); - if (cache != null) { - cache.remove(item); - } - - new MemberRefInfoAccessor(item).setNameAndTypeIndex(this.pool.addNameAndTypeInfo(newName, newType)); - - // update the cache - if (cache != null) { - cache.put(item, item); - } - } catch (Exception ex) { - throw new Error(ex); - } - - // make sure the change worked - assert (newName.equals(getMemberrefName(memberrefIndex))); - assert (newType.equals(getMemberrefType(memberrefIndex))); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - public void changeClassName(int classNameIndex, String newName) { - // NOTE: when changing values, we always need to copy-on-write - try { - // get the class item - Object item = getItem(classNameIndex).getItem(); - - // update the cache - HashMap cache = getCache(); - if (cache != null) { - cache.remove(item); - } - - // add the new name and repoint the name-and-type to it - new ClassInfoAccessor(item).setNameIndex(this.pool.addUtf8Info(newName)); - - // update the cache - if (cache != null) { - cache.put(item, item); - } - } catch (Exception ex) { - throw new Error(ex); - } - } - - public String dump() { - StringBuilder buf = new StringBuilder(); - for (int i = 1; i < this.pool.getSize(); i++) { - buf.append(String.format("%4d", i)); - buf.append(" "); - buf.append(getItem(i)); - buf.append("\n"); - } - return buf.toString(); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/InfoType.java b/src/main/java/cuchaz/enigma/bytecode/InfoType.java deleted file mode 100644 index 9013d58..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/InfoType.java +++ /dev/null @@ -1,266 +0,0 @@ -/******************************************************************************* - * 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.bytecode; - -import com.google.common.collect.Maps; -import cuchaz.enigma.bytecode.accessors.*; - -import java.util.Collection; -import java.util.Map; - -public enum InfoType { - - Utf8Info(1), - IntegerInfo(3), - FloatInfo(4), - LongInfo(5), - DoubleInfo(6), - ClassInfo(7) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getNameIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - ClassInfoAccessor accessor = new ClassInfoAccessor(entry.getItem()); - ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); - return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag(); - } - }, - StringInfo(8) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getStringIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - accessor.setStringIndex(remapIndex(map, accessor.getStringIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - StringInfoAccessor accessor = new StringInfoAccessor(entry.getItem()); - ConstInfoAccessor stringEntry = pool.getItem(accessor.getStringIndex()); - return stringEntry != null && stringEntry.getTag() == Utf8Info.getTag(); - } - }, - FieldRefInfo(9) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getClassIndex()); - gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - accessor.setClassIndex(remapIndex(map, accessor.getClassIndex())); - accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MemberRefInfoAccessor accessor = new MemberRefInfoAccessor(entry.getItem()); - ConstInfoAccessor classEntry = pool.getItem(accessor.getClassIndex()); - ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); - return classEntry != null && classEntry.getTag() == ClassInfo.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); - } - }, - // same as FieldRefInfo - MethodRefInfo(10) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - FieldRefInfo.gatherIndexTree(indices, editor, entry); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - FieldRefInfo.remapIndices(map, entry); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - return FieldRefInfo.subIndicesAreValid(entry, pool); - } - }, - // same as FieldRefInfo - InterfaceMethodRefInfo(11) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - FieldRefInfo.gatherIndexTree(indices, editor, entry); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - FieldRefInfo.remapIndices(map, entry); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - return FieldRefInfo.subIndicesAreValid(entry, pool); - } - }, - NameAndTypeInfo(12) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getNameIndex()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - accessor.setNameIndex(remapIndex(map, accessor.getNameIndex())); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - NameAndTypeInfoAccessor accessor = new NameAndTypeInfoAccessor(entry.getItem()); - ConstInfoAccessor nameEntry = pool.getItem(accessor.getNameIndex()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - return nameEntry != null && nameEntry.getTag() == Utf8Info.getTag() && typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); - } - }, - MethodHandleInfo(15) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - gatherIndexTree(indices, editor, accessor.getMethodRefIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - accessor.setMethodRefIndex(remapIndex(map, accessor.getMethodRefIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MethodHandleInfoAccessor accessor = new MethodHandleInfoAccessor(entry.getItem()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - ConstInfoAccessor methodRefEntry = pool.getItem(accessor.getMethodRefIndex()); - return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag() && methodRefEntry != null && methodRefEntry.getTag() == MethodRefInfo.getTag(); - } - }, - MethodTypeInfo(16) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - accessor.setTypeIndex(remapIndex(map, accessor.getTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - MethodTypeInfoAccessor accessor = new MethodTypeInfoAccessor(entry.getItem()); - ConstInfoAccessor typeEntry = pool.getItem(accessor.getTypeIndex()); - return typeEntry != null && typeEntry.getTag() == Utf8Info.getTag(); - } - }, - InvokeDynamicInfo(18) { - @Override - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - gatherIndexTree(indices, editor, accessor.getBootstrapIndex()); - gatherIndexTree(indices, editor, accessor.getNameAndTypeIndex()); - } - - @Override - public void remapIndices(Map map, ConstInfoAccessor entry) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - accessor.setBootstrapIndex(remapIndex(map, accessor.getBootstrapIndex())); - accessor.setNameAndTypeIndex(remapIndex(map, accessor.getNameAndTypeIndex())); - } - - @Override - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - InvokeDynamicInfoAccessor accessor = new InvokeDynamicInfoAccessor(entry.getItem()); - ConstInfoAccessor bootstrapEntry = pool.getItem(accessor.getBootstrapIndex()); - ConstInfoAccessor nameAndTypeEntry = pool.getItem(accessor.getNameAndTypeIndex()); - return bootstrapEntry != null && bootstrapEntry.getTag() == Utf8Info.getTag() && nameAndTypeEntry != null && nameAndTypeEntry.getTag() == NameAndTypeInfo.getTag(); - } - }; - - private static Map types; - - static { - types = Maps.newTreeMap(); - for (InfoType type : values()) { - types.put(type.getTag(), type); - } - } - - private int tag; - - InfoType(int tag) { - this.tag = tag; - } - - public static InfoType getByTag(int tag) { - return types.get(tag); - } - - public static void gatherIndexTree(Collection indices, ConstPoolEditor editor, int index) { - // add own index - indices.add(index); - - // recurse - ConstInfoAccessor entry = editor.getItem(index); - entry.getType().gatherIndexTree(indices, editor, entry); - } - - private static int remapIndex(Map map, int index) { - Integer newIndex = map.get(index); - if (newIndex == null) { - newIndex = index; - } - return newIndex; - } - - public int getTag() { - return this.tag; - } - - public void gatherIndexTree(Collection indices, ConstPoolEditor editor, ConstInfoAccessor entry) { - // by default, do nothing - } - - public void remapIndices(Map map, ConstInfoAccessor entry) { - // by default, do nothing - } - - public boolean subIndicesAreValid(ConstInfoAccessor entry, ConstPoolEditor pool) { - // by default, everything is good - return true; - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java b/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java deleted file mode 100644 index 3f819ab..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/MethodParametersAttribute.java +++ /dev/null @@ -1,87 +0,0 @@ -/******************************************************************************* - * 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.bytecode; - -import javassist.bytecode.AttributeInfo; -import javassist.bytecode.ConstPool; -import javassist.bytecode.MethodInfo; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -public class MethodParametersAttribute extends AttributeInfo { - - private MethodParametersAttribute(ConstPool pool, List parameterNameIndices) { - super(pool, "MethodParameters", writeStruct(parameterNameIndices)); - } - - public static void updateClass(MethodInfo info, List names) { - - // add the names to the class const pool - ConstPool constPool = info.getConstPool(); - List parameterNameIndices = new ArrayList<>(); - for (String name : names) { - if (name != null) { - parameterNameIndices.add(constPool.addUtf8Info(name)); - } else { - parameterNameIndices.add(0); - } - } - - // add the attribute to the method - info.addAttribute(new MethodParametersAttribute(constPool, parameterNameIndices)); - } - - private static byte[] writeStruct(List parameterNameIndices) { - // JVM 8 Spec says the struct looks like this: - // http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.24 - // uint8 num_params - // for each param: - // uint16 name_index -> points to UTF8 entry in constant pool, or 0 for no entry - // uint16 access_flags -> don't care, just set to 0 - - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(buf); - - // NOTE: java hates unsigned integers, so we have to be careful here - // the writeShort(), writeByte() methods will read 16,8 low-order bits from the int argument - // as long as the int argument is in range of the unsigned short/byte type, it will be written as an unsigned short/byte - // if the int is out of range, the byte stream won't look the way we want and weird things will happen - final int SIZEOF_UINT8 = 1; - final int SIZEOF_UINT16 = 2; - final int MAX_UINT8 = (1 << 8) - 1; - final int MAX_UINT16 = (1 << 16) - 1; - - try { - assert (parameterNameIndices.size() >= 0 && parameterNameIndices.size() <= MAX_UINT8); - out.writeByte(parameterNameIndices.size()); - - for (Integer index : parameterNameIndices) { - assert (index >= 0 && index <= MAX_UINT16); - out.writeShort(index); - - // just write 0 for the access flags - out.writeShort(0); - } - - out.close(); - byte[] data = buf.toByteArray(); - assert (data.length == SIZEOF_UINT8 + parameterNameIndices.size() * (SIZEOF_UINT16 + SIZEOF_UINT16)); - return data; - } catch (IOException ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java deleted file mode 100644 index eaa6e90..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/ClassInfoAccessor.java +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import java.lang.reflect.Field; - -public class ClassInfoAccessor { - - private static Class clazz; - private static Field nameIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.ClassInfo"); - nameIndex = clazz.getDeclaredField("name"); - nameIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public ClassInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getNameIndex() { - try { - return (Integer) nameIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameIndex(int val) { - try { - nameIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java deleted file mode 100644 index 27d991a..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/ConstInfoAccessor.java +++ /dev/null @@ -1,124 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import com.google.common.base.Charsets; -import cuchaz.enigma.bytecode.InfoType; - -import java.io.*; -import java.lang.reflect.Field; -import java.lang.reflect.Method; - -public class ConstInfoAccessor { - - private static Class clazz; - private static Field index; - private static Method getTag; - - static { - try { - clazz = Class.forName("javassist.bytecode.ConstInfo"); - index = clazz.getDeclaredField("index"); - index.setAccessible(true); - getTag = clazz.getMethod("getTag"); - getTag.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public ConstInfoAccessor(Object item) { - if (item == null) { - throw new IllegalArgumentException("item cannot be null!"); - } - this.item = item; - } - - public Object getItem() { - return this.item; - } - - public int getIndex() { - try { - return (Integer) index.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getTag() { - try { - return (Integer) getTag.invoke(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public ConstInfoAccessor copy() { - return new ConstInfoAccessor(copyItem()); - } - - public Object copyItem() { - // I don't know of a simpler way to copy one of these silly things... - try { - // serialize the item - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream out = new DataOutputStream(buf); - write(out); - - // deserialize the item - DataInputStream in = new DataInputStream(new ByteArrayInputStream(buf.toByteArray())); - Object item = new ConstInfoAccessor(in).getItem(); - in.close(); - - return item; - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void write(DataOutputStream out) throws IOException { - try { - out.writeUTF(this.item.getClass().getName()); - out.writeInt(getIndex()); - - Method method = this.item.getClass().getMethod("write", DataOutputStream.class); - method.setAccessible(true); - method.invoke(this.item, out); - } catch (IOException ex) { - throw ex; - } catch (Exception ex) { - throw new Error(ex); - } - } - - @Override - public String toString() { - try { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - PrintWriter out = new PrintWriter(new OutputStreamWriter(buf, Charsets.UTF_8)); - Method print = this.item.getClass().getMethod("print", PrintWriter.class); - print.setAccessible(true); - print.invoke(this.item, out); - out.close(); - return buf.toString("UTF-8").replace("\n", ""); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public InfoType getType() { - return InfoType.getByTag(getTag()); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java deleted file mode 100644 index aef3532..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/InvokeDynamicInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import java.lang.reflect.Field; - -public class InvokeDynamicInfoAccessor { - - private static Class clazz; - private static Field bootstrapIndex; - private static Field nameAndTypeIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.InvokeDynamicInfo"); - bootstrapIndex = clazz.getDeclaredField("bootstrap"); - bootstrapIndex.setAccessible(true); - nameAndTypeIndex = clazz.getDeclaredField("nameAndType"); - nameAndTypeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public InvokeDynamicInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getBootstrapIndex() { - try { - return (Integer) bootstrapIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setBootstrapIndex(int val) { - try { - bootstrapIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getNameAndTypeIndex() { - try { - return (Integer) nameAndTypeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameAndTypeIndex(int val) { - try { - nameAndTypeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java deleted file mode 100644 index 058bb45..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MemberRefInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import java.lang.reflect.Field; - -public class MemberRefInfoAccessor { - - private static Class clazz; - private static Field classIndex; - private static Field nameAndTypeIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.MemberrefInfo"); - classIndex = clazz.getDeclaredField("classIndex"); - classIndex.setAccessible(true); - nameAndTypeIndex = clazz.getDeclaredField("nameAndTypeIndex"); - nameAndTypeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public MemberRefInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getClassIndex() { - try { - return (Integer) classIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setClassIndex(int val) { - try { - classIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getNameAndTypeIndex() { - try { - return (Integer) nameAndTypeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameAndTypeIndex(int val) { - try { - nameAndTypeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java deleted file mode 100644 index 985e792..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodHandleInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import java.lang.reflect.Field; - -public class MethodHandleInfoAccessor { - - private static Class clazz; - private static Field kindIndex; - private static Field indexIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.MethodHandleInfo"); - kindIndex = clazz.getDeclaredField("refKind"); - kindIndex.setAccessible(true); - indexIndex = clazz.getDeclaredField("refIndex"); - indexIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public MethodHandleInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getTypeIndex() { - try { - return (Integer) kindIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setTypeIndex(int val) { - try { - kindIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getMethodRefIndex() { - try { - return (Integer) indexIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setMethodRefIndex(int val) { - try { - indexIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java deleted file mode 100644 index 10b0cb0..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/MethodTypeInfoAccessor.java +++ /dev/null @@ -1,57 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import java.lang.reflect.Field; - -public class MethodTypeInfoAccessor { - - private static Class clazz; - private static Field descriptorIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.MethodTypeInfo"); - descriptorIndex = clazz.getDeclaredField("descriptor"); - descriptorIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public MethodTypeInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getTypeIndex() { - try { - return (Integer) descriptorIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setTypeIndex(int val) { - try { - descriptorIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java deleted file mode 100644 index cc7fdbe..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/NameAndTypeInfoAccessor.java +++ /dev/null @@ -1,75 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import java.lang.reflect.Field; - -public class NameAndTypeInfoAccessor { - - private static Class clazz; - private static Field nameIndex; - private static Field typeIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.NameAndTypeInfo"); - nameIndex = clazz.getDeclaredField("memberName"); - nameIndex.setAccessible(true); - typeIndex = clazz.getDeclaredField("typeDescriptor"); - typeIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public NameAndTypeInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getNameIndex() { - try { - return (Integer) nameIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setNameIndex(int val) { - try { - nameIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public int getTypeIndex() { - try { - return (Integer) typeIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setTypeIndex(int val) { - try { - typeIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java deleted file mode 100644 index 5c68d4a..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/StringInfoAccessor.java +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -import java.lang.reflect.Field; - -public class StringInfoAccessor { - - private static Class clazz; - private static Field stringIndex; - - static { - try { - clazz = Class.forName("javassist.bytecode.StringInfo"); - stringIndex = clazz.getDeclaredField("string"); - stringIndex.setAccessible(true); - } catch (Exception ex) { - throw new Error(ex); - } - } - - private Object item; - - public StringInfoAccessor(Object item) { - this.item = item; - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } - - public int getStringIndex() { - try { - return (Integer) stringIndex.get(this.item); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public void setStringIndex(int val) { - try { - stringIndex.set(this.item, val); - } catch (Exception ex) { - throw new Error(ex); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java b/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java deleted file mode 100644 index cc3b41b..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/accessors/Utf8InfoAccessor.java +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************* - * 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.bytecode.accessors; - -public class Utf8InfoAccessor { - - private static Class clazz; - - static { - try { - clazz = Class.forName("javassist.bytecode.Utf8Info"); - } catch (Exception ex) { - throw new Error(ex); - } - } - - public static boolean isType(ConstInfoAccessor accessor) { - return clazz.isAssignableFrom(accessor.getItem().getClass()); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java deleted file mode 100644 index 4ac5a8b..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/ClassTranslator.java +++ /dev/null @@ -1,161 +0,0 @@ -/******************************************************************************* - * 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.bytecode.translators; - -import cuchaz.enigma.bytecode.ClassRenamer; -import cuchaz.enigma.bytecode.ConstPoolEditor; -import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.CtField; -import javassist.CtMethod; -import javassist.bytecode.*; - -public class ClassTranslator { - - public static void translate(Translator translator, CtClass c) { - - // NOTE: the order of these translations is very important - - // translate all the field and method references in the code by editing the constant pool - ConstPool constants = c.getClassFile().getConstPool(); - ConstPoolEditor editor = new ConstPoolEditor(constants); - for (int i = 1; i < constants.getSize(); i++) { - switch (constants.getTag(i)) { - - case ConstPool.CONST_Fieldref: { - - // translate the name and type - FieldEntry entry = EntryFactory.getFieldEntry( - Descriptor.toJvmName(constants.getFieldrefClassName(i)), - constants.getFieldrefName(i), - constants.getFieldrefType(i) - ); - FieldEntry translatedEntry = translator.translateEntry(entry); - if (!entry.equals(translatedEntry)) { - editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getType().toString()); - } - } - break; - - case ConstPool.CONST_Methodref: - case ConstPool.CONST_InterfaceMethodref: { - - // translate the name and type (ie signature) - BehaviorEntry entry = EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(editor.getMemberrefClassname(i)), - editor.getMemberrefName(i), - editor.getMemberrefType(i) - ); - BehaviorEntry translatedEntry = translator.translateEntry(entry); - if (!entry.equals(translatedEntry)) { - editor.changeMemberrefNameAndType(i, translatedEntry.getName(), translatedEntry.getSignature().toString()); - } - } - break; - default: - break; - } - } - - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(c.getName())); - Mappings.EntryModifier modifier = translator.getModifier(classEntry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(c, modifier); - - // translate all the fields - for (CtField field : c.getDeclaredFields()) { - - // translate the name - FieldEntry entry = EntryFactory.getFieldEntry(field); - String translatedName = translator.translate(entry); - modifier = translator.getModifier(entry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(field, modifier); - - if (translatedName != null) { - field.setName(translatedName); - } - - // translate the type - Type translatedType = translator.translateType(entry.getType()); - field.getFieldInfo().setDescriptor(translatedType.toString()); - } - - // translate all the methods and constructors - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - BehaviorEntry entry = EntryFactory.getBehaviorEntry(behavior); - - modifier = translator.getModifier(entry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(behavior, modifier); - - if (behavior instanceof CtMethod) { - CtMethod method = (CtMethod) behavior; - - // translate the name - String translatedName = translator.translate(entry); - if (translatedName != null) { - method.setName(translatedName); - } - } - - if (entry.getSignature() != null) { - // translate the signature - Signature translatedSignature = translator.translateSignature(entry.getSignature()); - behavior.getMethodInfo().setDescriptor(translatedSignature.toString()); - } - } - - // translate the EnclosingMethod attribute - EnclosingMethodAttribute enclosingMethodAttr = (EnclosingMethodAttribute) c.getClassFile().getAttribute(EnclosingMethodAttribute.tag); - if (enclosingMethodAttr != null) { - - if (enclosingMethodAttr.methodIndex() == 0) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry(Descriptor.toJvmName(enclosingMethodAttr.className())); - BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry); - c.getClassFile().addAttribute(new EnclosingMethodAttribute( - constants, - deobfBehaviorEntry.getClassName() - )); - } else { - BehaviorEntry obfBehaviorEntry = EntryFactory.getBehaviorEntry( - Descriptor.toJvmName(enclosingMethodAttr.className()), - enclosingMethodAttr.methodName(), - enclosingMethodAttr.methodDescriptor() - ); - BehaviorEntry deobfBehaviorEntry = translator.translateEntry(obfBehaviorEntry); - c.getClassFile().addAttribute(new EnclosingMethodAttribute( - constants, - deobfBehaviorEntry.getClassName(), - deobfBehaviorEntry.getName(), - deobfBehaviorEntry.getSignature().toString() - )); - } - } - - // translate all the class names referenced in the code - // the above code only changed method/field/reference names and types, but not the rest of the class references - ClassRenamer.renameClasses(c, translator); - - // translate the source file attribute too - ClassEntry deobfClassEntry = translator.translateEntry(classEntry); - if (deobfClassEntry != null) { - String sourceFile = Descriptor.toJvmName(deobfClassEntry.getOutermostClassEntry().getSimpleName()) + ".java"; - c.getClassFile().addAttribute(new SourceFileAttribute(constants, sourceFile)); - } - InnerClassesAttribute attr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (attr != null) - InnerClassWriter.changeModifier(c, attr, translator); - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java b/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java deleted file mode 100644 index 0e35938..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/InnerClassWriter.java +++ /dev/null @@ -1,144 +0,0 @@ -/******************************************************************************* - * 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.bytecode.translators; - -import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.bytecode.ClassRenamer; -import cuchaz.enigma.mapping.*; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.NotFoundException; -import javassist.bytecode.*; - -import java.util.Collection; -import java.util.List; - -public class InnerClassWriter { - - // FIXME: modifier is not applied to inner class - public static void changeModifier(CtClass c, InnerClassesAttribute attr, Translator translator) { - ClassPool pool = c.getClassPool(); - for (int i = 0; i < attr.tableLength(); i++) { - - String innerName = attr.innerClass(i); - // get the inner class full name (which has already been translated) - ClassEntry classEntry = new ClassEntry(Descriptor.toJvmName(innerName)); - try { - CtClass innerClass = pool.get(innerName); - Mappings.EntryModifier modifier = translator.getModifier(classEntry); - if (modifier != null && modifier != Mappings.EntryModifier.UNCHANGED) - ClassRenamer.applyModifier(innerClass, modifier); - } catch (NotFoundException e) { - // This shouldn't be possible in theory - //e.printStackTrace(); - } - } - } - - public static void write(JarIndex index, CtClass c) { - - // don't change anything if there's already an attribute there - InnerClassesAttribute oldAttr = (InnerClassesAttribute) c.getClassFile().getAttribute(InnerClassesAttribute.tag); - if (oldAttr != null) { - // bail! - return; - } - - ClassEntry obfClassEntry = EntryFactory.getClassEntry(c); - List obfClassChain = index.getObfClassChain(obfClassEntry); - - boolean isInnerClass = obfClassChain.size() > 1; - if (isInnerClass) { - - // it's an inner class, rename it to the fully qualified name - c.setName(obfClassEntry.buildClassEntry(obfClassChain).getName()); - - BehaviorEntry caller = index.getAnonymousClassCaller(obfClassEntry); - if (caller != null) { - - // write the enclosing method attribute - if (caller.getName().equals("")) { - c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName())); - } else { - c.getClassFile().addAttribute(new EnclosingMethodAttribute(c.getClassFile().getConstPool(), caller.getClassName(), caller.getName(), caller.getSignature().toString())); - } - } - } - - // does this class have any inner classes? - Collection obfInnerClassEntries = index.getInnerClasses(obfClassEntry); - - if (isInnerClass || !obfInnerClassEntries.isEmpty()) { - - // create an inner class attribute - InnerClassesAttribute attr = new InnerClassesAttribute(c.getClassFile().getConstPool()); - c.getClassFile().addAttribute(attr); - - // write the ancestry, but not the outermost class - for (int i = 1; i < obfClassChain.size(); i++) { - ClassEntry obfInnerClassEntry = obfClassChain.get(i); - writeInnerClass(index, attr, obfClassChain, obfInnerClassEntry); - - // update references to use the fully qualified inner class name - c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(obfClassChain).getName()); - } - - // write the inner classes - for (ClassEntry obfInnerClassEntry : obfInnerClassEntries) { - - // extend the class chain - List extendedObfClassChain = Lists.newArrayList(obfClassChain); - extendedObfClassChain.add(obfInnerClassEntry); - - writeInnerClass(index, attr, extendedObfClassChain, obfInnerClassEntry); - - // update references to use the fully qualified inner class name - c.replaceClassName(obfInnerClassEntry.getName(), obfInnerClassEntry.buildClassEntry(extendedObfClassChain).getName()); - } - } - } - - private static void writeInnerClass(JarIndex index, InnerClassesAttribute attr, List obfClassChain, ClassEntry obfClassEntry) { - - // get the new inner class name - ClassEntry obfInnerClassEntry = obfClassEntry.buildClassEntry(obfClassChain); - ClassEntry obfOuterClassEntry = obfInnerClassEntry.getOuterClassEntry(); - - // here's what the JVM spec says about the InnerClasses attribute - // append(inner, parent, 0 if anonymous else simple name, flags); - - // update the attribute with this inner class - ConstPool constPool = attr.getConstPool(); - int innerClassIndex = constPool.addClassInfo(obfInnerClassEntry.getName()); - int parentClassIndex = constPool.addClassInfo(obfOuterClassEntry.getName()); - int innerClassNameIndex = 0; - int accessFlags = AccessFlag.PUBLIC; - // TODO: need to figure out if we can put static or not - if (!index.isAnonymousClass(obfClassEntry)) { - innerClassNameIndex = constPool.addUtf8Info(obfInnerClassEntry.getInnermostClassName()); - } - - attr.append(innerClassIndex, parentClassIndex, innerClassNameIndex, accessFlags); - - /* DEBUG - System.out.println(String.format("\tOBF: %s -> ATTR: %s,%s,%s (replace %s with %s)", - obfClassEntry, - attr.innerClass(attr.tableLength() - 1), - attr.outerClass(attr.tableLength() - 1), - attr.innerName(attr.tableLength() - 1), - Constants.NonePackage + "/" + obfInnerClassName, - obfClassEntry.getName() - )); - */ - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java deleted file mode 100644 index 51b3d2d..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableTranslator.java +++ /dev/null @@ -1,142 +0,0 @@ -/******************************************************************************* - * 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.bytecode.translators; - -import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.bytecode.*; - -public class LocalVariableTranslator { - - public static void translate(Translator translator, CtClass c) { - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - // if there's a local variable table, just rename everything to v1, v2, v3, ... for now - CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); - if (codeAttribute == null) { - continue; - } - - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - ConstPool constants = c.getClassFile().getConstPool(); - - LocalVariableAttribute table = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag); - if (table != null) { - renameLVT(translator, behaviorEntry, constants, table, c); - } - - LocalVariableTypeAttribute typeTable = (LocalVariableTypeAttribute) codeAttribute.getAttribute(LocalVariableAttribute.typeTag); - if (typeTable != null) { - renameLVTT(typeTable, table); - } - } - } - - // DEBUG - @SuppressWarnings("unused") - private static void dumpTable(LocalVariableAttribute table) { - for (int i = 0; i < table.tableLength(); i++) { - System.out.println(String.format("\t%d (%d): %s %s", - i, table.index(i), table.variableName(i), table.descriptor(i) - )); - } - } - - private static void renameLVT(Translator translator, BehaviorEntry behaviorEntry, ConstPool constants, LocalVariableAttribute table, CtClass ctClass) { - - // skip empty tables - if (table.tableLength() <= 0) { - return; - } - - // where do we start counting variables? - int starti = 0; - if (table.variableName(0).equals("this")) { - // skip the "this" variable - starti++; - } - - // rename method arguments first - int numArgs = 0; - if (behaviorEntry.getSignature() != null) { - numArgs = behaviorEntry.getSignature().getArgumentTypes().size(); - boolean isNestedClassConstructor = false; - - // If the behavior is a constructor and if it have more than one arg, it's probably from a nested! - if (behaviorEntry instanceof ConstructorEntry && behaviorEntry.getClassEntry() != null && behaviorEntry.getClassEntry().isInnerClass() && numArgs >= 1) { - // Get the first arg type - Type firstArg = behaviorEntry.getSignature().getArgumentTypes().get(0); - - // If the arg is a class and if the class name match the outer class name of the constructor, it's definitely a constructor of a nested class - if (firstArg.isClass() && firstArg.getClassEntry().equals(behaviorEntry.getClassEntry().getOuterClassEntry())) { - isNestedClassConstructor = true; - numArgs--; - } - } - - for (int i = starti; i < starti + numArgs && i < table.tableLength(); i++) { - int argi = i - starti; - if (ctClass.isEnum()) - argi += 2; - String argName = translator.translate(new ArgumentEntry(behaviorEntry, argi, "")); - if (argName == null) { - int argIndex = isNestedClassConstructor ? argi + 1 : argi; - if (ctClass.isEnum()) - argIndex -= 2; - Type argType = behaviorEntry.getSignature().getArgumentTypes().get(argIndex); - // Unfortunately each of these have different name getters, so they have different code paths - if (argType.isPrimitive()) { - Type.Primitive argCls = argType.getPrimitive(); - argName = "a" + argCls.name() + (argIndex + 1); - } else if (argType.isArray()) { - // List types would require this whole block again, so just go with aListx - argName = "aList" + (argIndex + 1); - } else if (argType.isClass()) { - ClassEntry argClsTrans = translator.translateEntry(argType.getClassEntry()); - argName = "a" + argClsTrans.getSimpleName().replace("$", "") + (argIndex + 1); - } else { - argName = "a" + (argIndex + 1); - } - } - renameVariable(table, i, constants.addUtf8Info(argName)); - } - } - - // then rename the rest of the args, if any - for (int i = starti + numArgs; i < table.tableLength(); i++) { - int firstIndex = Math.min(table.index(starti + numArgs), table.index(i)); - renameVariable(table, i, constants.addUtf8Info("v" + (table.index(i) - firstIndex + 1))); - } - } - - private static void renameLVTT(LocalVariableTypeAttribute typeTable, LocalVariableAttribute table) { - // rename args to the same names as in the LVT - for (int i = 0; i < typeTable.tableLength(); i++) { - renameVariable(typeTable, i, getNameIndex(table, typeTable.index(i))); - } - } - - private static void renameVariable(LocalVariableAttribute table, int i, int stringId) { - // based off of LocalVariableAttribute.nameIndex() - ByteArray.write16bit(stringId, table.get(), i * 10 + 6); - } - - private static int getNameIndex(LocalVariableAttribute table, int index) { - for (int i = 0; i < table.tableLength(); i++) { - if (table.index(i) == index) { - return table.nameIndex(i); - } - } - return 0; - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java deleted file mode 100644 index 4e632b9..0000000 --- a/src/main/java/cuchaz/enigma/bytecode/translators/MethodParameterTranslator.java +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************* - * 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.bytecode.translators; - -import cuchaz.enigma.bytecode.MethodParametersAttribute; -import cuchaz.enigma.mapping.*; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.bytecode.CodeAttribute; -import javassist.bytecode.LocalVariableAttribute; - -import java.util.ArrayList; -import java.util.List; - -public class MethodParameterTranslator { - - public static void translate(Translator translator, CtClass c) { - - // Procyon will read method arguments from the "MethodParameters" attribute, so write those - for (CtBehavior behavior : c.getDeclaredBehaviors()) { - - // if there's a local variable table here, don't write a MethodParameters attribute - // let the local variable writer deal with it instead - // procyon starts doing really weird things if we give it both attributes - CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute(); - if (codeAttribute != null && codeAttribute.getAttribute(LocalVariableAttribute.tag) != null) { - continue; - } - - BehaviorEntry behaviorEntry = EntryFactory.getBehaviorEntry(behavior); - - // get the number of arguments - Signature signature = behaviorEntry.getSignature(); - if (signature == null) { - // static initializers have no signatures, or arguments - continue; - } - int numParams = signature.getArgumentTypes().size(); - if (numParams <= 0) { - continue; - } - - // get the list of argument names - List names = new ArrayList<>(numParams); - for (int i = 0; i < numParams; i++) { - names.add(translator.translate(new ArgumentEntry(behaviorEntry, i, ""))); - } - - // save the mappings to the class - MethodParametersAttribute.updateClass(behavior.getMethodInfo(), names); - } - } -} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java new file mode 100644 index 0000000..24bfe24 --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * 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.bytecode.translators; + +import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.bytecode.AccessFlags; +import cuchaz.enigma.mapping.*; +import org.objectweb.asm.*; + +import java.util.regex.Pattern; + +public class TranslationClassVisitor extends ClassVisitor { + private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*"); + + private final Translator translator; + private final JarIndex jarIndex; + private final ReferencedEntryPool entryPool; + + private ClassDefEntry obfClassEntry; + + public TranslationClassVisitor(Translator translator, JarIndex jarIndex, ReferencedEntryPool entryPool, int api, ClassVisitor cv) { + super(api, cv); + this.translator = translator; + this.jarIndex = jarIndex; + this.entryPool = entryPool; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) { + signature = signature.replaceAll(":Ljava/lang/Object;:", "::"); + } + obfClassEntry = new ClassDefEntry(name, new AccessFlags(access)); + ClassDefEntry entry = translator.getTranslatedClassDef(obfClassEntry); + ClassEntry superEntry = translator.getTranslatedClass(entryPool.getClass(superName)); + String[] translatedInterfaces = new String[interfaces.length]; + for (int i = 0; i < interfaces.length; i++) { + translatedInterfaces[i] = translator.getTranslatedClass(entryPool.getClass(interfaces[i])).getName(); + } + super.visit(version, entry.getAccess().getFlags(), entry.getName(), signature, superEntry.getName(), translatedInterfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + FieldDefEntry entry = new FieldDefEntry(obfClassEntry, name, new TypeDescriptor(desc), new AccessFlags(access)); + FieldDefEntry translatedEntry = translator.getTranslatedFieldDef(entry); + return super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodDefEntry entry = new MethodDefEntry(obfClassEntry, name, new MethodDescriptor(desc), new AccessFlags(access)); + MethodDefEntry translatedEntry = translator.getTranslatedMethodDef(entry); + if (jarIndex.getBridgedMethod(entry) != null) { + translatedEntry.getAccess().setBridged(); + } + MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), name, desc, signature, exceptions); + return new TranslationMethodVisitor(translator, translatedEntry, api, mv); + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + if (name != null) { + ClassEntry ownerEntry = translator.getTranslatedClass(entryPool.getClass(owner)); + ClassEntry entry = translator.getTranslatedClass(entryPool.getClass(name)); + String translatedDesc = translator.getTranslatedTypeDesc(new TypeDescriptor(desc)).toString(); + super.visitOuterClass(ownerEntry.getName(), entry.getName(), translatedDesc); + } else { + super.visitOuterClass(owner, name, desc); + } + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // TODO: Implement + return super.visitAnnotation(desc, visible); + } + + @Override + public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + // TODO: Implement + return super.visitTypeAnnotation(typeRef, typePath, desc, visible); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + // If this is not an anonymous class + if (innerName != null && outerName != null) { + ClassDefEntry translatedEntry = translator.getTranslatedClassDef(new ClassDefEntry(innerName, new AccessFlags(access))); + ClassEntry outerEntry = translator.getTranslatedClass(entryPool.getClass(outerName)); + ClassEntry innerEntry = translator.getTranslatedClass(entryPool.getClass(innerName)); + super.visitInnerClass(translatedEntry.getName(), outerEntry.getName(), innerEntry.getName(), translatedEntry.getAccess().getFlags()); + } else { + super.visitInnerClass(name, outerName, innerName, access); + } + } +} diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java new file mode 100644 index 0000000..e40becc --- /dev/null +++ b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java @@ -0,0 +1,82 @@ +package cuchaz.enigma.bytecode.translators; + +import cuchaz.enigma.mapping.*; +import org.objectweb.asm.*; + +import java.util.Locale; + +public class TranslationMethodVisitor extends MethodVisitor { + private final MethodDefEntry methodEntry; + private final Translator translator; + + public TranslationMethodVisitor(Translator translator, MethodDefEntry methodEntry, int api, MethodVisitor mv) { + super(api, mv); + this.translator = translator; + this.methodEntry = methodEntry; + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc)); + FieldEntry translatedEntry = translator.getTranslatedField(entry); + if (translatedEntry != null) { + super.visitFieldInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString()); + } else { + super.visitFieldInsn(opcode, owner, name, desc); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)); + MethodEntry translatedEntry = translator.getTranslatedMethod(entry); + if (translatedEntry != null) { + super.visitMethodInsn(opcode, translatedEntry.getClassName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + @Override + public void visitAttribute(Attribute attr) { + // TODO: Implement + super.visitAttribute(attr); + } + + @Override + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + // TODO: Implement + return super.visitAnnotation(desc, visible); + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + LocalVariableDefEntry entry = new LocalVariableDefEntry(methodEntry, index, name, new TypeDescriptor(desc)); + LocalVariableDefEntry translatedEntry = translator.getTranslatedVariableDef(entry); + String translatedName = translatedEntry.getName(); + // TODO: Better name inference + if (translatedName.equals(entry.getName())) { + TypeDescriptor argDesc = translatedEntry.getDesc(); + int nameIndex = translatedEntry.getNamedIndex() + 1; + String prefix = translatedEntry.getNamedIndex() < methodEntry.getDesc().getArgumentDescs().size() ? "a" : "v"; + StringBuilder nameBuilder = new StringBuilder(prefix); + // Unfortunately each of these have different name getters, so they have different code paths + if (argDesc.isPrimitive()) { + TypeDescriptor.Primitive argCls = argDesc.getPrimitive(); + nameBuilder.append(argCls.name()); + } else if (argDesc.isArray()) { + // List types would require this whole block again, so just go with aListx + nameBuilder.append(nameIndex); + } else if (argDesc.isType()) { + String typeName = argDesc.getOwnerEntry().getSimpleName().replace("$", ""); + typeName = typeName.substring(0, 1).toUpperCase(Locale.ROOT) + typeName.substring(1); + nameBuilder.append(typeName); + } + if (methodEntry.getDesc().getArgumentDescs().size() > 1) { + nameBuilder.append(nameIndex); + } + translatedName = nameBuilder.toString(); + } + super.visitLocalVariable(translatedName, translatedEntry.getDesc().toString(), signature, start, end, index); + } +} diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java index f76dc89..97deaf3 100644 --- a/src/main/java/cuchaz/enigma/gui/CodeReader.java +++ b/src/main/java/cuchaz/enigma/gui/CodeReader.java @@ -162,7 +162,7 @@ public class CodeReader extends JEditorPane { // couldn't find the class declaration token, might be an anonymous class // look for any declaration in that class instead for (Entry entry : sourceIndex.declarations()) { - if (entry.getClassEntry().equals(classEntry)) { + if (entry.getOwnerClassEntry().equals(classEntry)) { token = sourceIndex.getDeclarationToken(entry); break; } diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java index 4a891cf..5d9f7cb 100644 --- a/src/main/java/cuchaz/enigma/gui/Gui.java +++ b/src/main/java/cuchaz/enigma/gui/Gui.java @@ -35,7 +35,6 @@ import cuchaz.enigma.mapping.*; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.utils.Utils; import de.sciss.syntaxpane.DefaultSyntaxKit; -import javassist.bytecode.Descriptor; import javax.swing.*; import javax.swing.text.BadLocationException; @@ -48,8 +47,10 @@ import java.awt.*; import java.awt.event.*; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Vector; import java.util.function.Function; public class Gui { @@ -438,14 +439,10 @@ public class Gui { showFieldEntry((FieldEntry) this.reference.entry); } else if (this.reference.entry instanceof MethodEntry) { showMethodEntry((MethodEntry) this.reference.entry); - } else if (this.reference.entry instanceof ConstructorEntry) { - showConstructorEntry((ConstructorEntry) this.reference.entry); - } else if (this.reference.entry instanceof ArgumentEntry) { - showArgumentEntry((ArgumentEntry) this.reference.entry); } else if (this.reference.entry instanceof LocalVariableEntry) { showLocalVariableEntry((LocalVariableEntry) this.reference.entry); } else { - throw new Error("Unknown entry type: " + this.reference.entry.getClass().getName()); + throw new Error("Unknown entry desc: " + this.reference.entry.getClass().getName()); } redraw(); @@ -453,10 +450,9 @@ public class Gui { private void showLocalVariableEntry(LocalVariableEntry entry) { addNameValue(infoPanel, "Variable", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); + addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "Method", entry.getOwnerEntry().getName()); addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); - addNameValue(infoPanel, "Type", entry.getType().toString()); } private void showClassEntry(ClassEntry entry) { @@ -466,32 +462,20 @@ public class Gui { private void showFieldEntry(FieldEntry entry) { addNameValue(infoPanel, "Field", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Type", entry.getType().toString()); + addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); + addNameValue(infoPanel, "TypeDescriptor", entry.getDesc().toString()); addModifierComboBox(infoPanel, "Modifier", entry); } private void showMethodEntry(MethodEntry entry) { - addNameValue(infoPanel, "Method", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); - - } - - private void showConstructorEntry(ConstructorEntry entry) { - addNameValue(infoPanel, "Constructor", entry.getClassEntry().getName()); - if (!entry.isStatic()) { - addNameValue(infoPanel, "Signature", entry.getSignature().toString()); - addModifierComboBox(infoPanel, "Modifier", entry); + if (entry.isConstructor()) { + addNameValue(infoPanel, "Constructor", entry.getOwnerClassEntry().getName()); + } else { + addNameValue(infoPanel, "Method", entry.getName()); + addNameValue(infoPanel, "Class", entry.getOwnerClassEntry().getName()); } - } - - private void showArgumentEntry(ArgumentEntry entry) { - addNameValue(infoPanel, "Argument", entry.getName()); - addNameValue(infoPanel, "Class", entry.getClassEntry().getName()); - addNameValue(infoPanel, "Method", entry.getBehaviorEntry().getName()); - addNameValue(infoPanel, "Index", Integer.toString(entry.getIndex())); + addNameValue(infoPanel, "MethodDescriptor", entry.getDesc().toString()); + addModifierComboBox(infoPanel, "Modifier", entry); } private void addNameValue(JPanel container, String name, String value) { @@ -532,8 +516,8 @@ public class Gui { reference = this.controller.getDeobfReference(token); boolean isClassEntry = isToken && reference.entry instanceof ClassEntry; boolean isFieldEntry = isToken && reference.entry instanceof FieldEntry; - boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry; - boolean isConstructorEntry = isToken && reference.entry instanceof ConstructorEntry; + boolean isMethodEntry = isToken && reference.entry instanceof MethodEntry && !((MethodEntry) reference.entry).isConstructor(); + boolean isConstructorEntry = isToken && reference.entry instanceof MethodEntry && ((MethodEntry) reference.entry).isConstructor(); boolean isInJar = isToken && this.controller.entryIsInJar(reference.entry); boolean isRenameable = isToken && this.controller.referenceIsRenameable(reference); @@ -710,16 +694,13 @@ public class Gui { if (reference.entry instanceof ClassEntry) { // look for calls to the default constructor // TODO: get a list of all the constructors and find calls to all of them - BehaviorReferenceTreeNode node = this.controller.getMethodReferences(new ConstructorEntry((ClassEntry) reference.entry, new Signature("()V"))); + MethodReferenceTreeNode node = this.controller.getMethodReferences(new MethodEntry((ClassEntry) reference.entry, "", new MethodDescriptor("()V"))); callsTree.setModel(new DefaultTreeModel(node)); } else if (reference.entry instanceof FieldEntry) { FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) reference.entry); callsTree.setModel(new DefaultTreeModel(node)); } else if (reference.entry instanceof MethodEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); - callsTree.setModel(new DefaultTreeModel(node)); - } else if (reference.entry instanceof ConstructorEntry) { - BehaviorReferenceTreeNode node = this.controller.getMethodReferences((ConstructorEntry) reference.entry); + MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) reference.entry); callsTree.setModel(new DefaultTreeModel(node)); } @@ -790,7 +771,6 @@ public class Gui { // package rename if (data instanceof String) { for (int i = 0; i < node.getChildCount(); i++) { - data = Descriptor.toJvmName((String) data); DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i); ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); @@ -807,15 +787,15 @@ public class Gui { } public void moveClassTree(EntryReference deobfReference, String newName) { - String oldEntry = deobfReference.entry.getClassEntry().getPackageName(); - String newEntry = new ClassEntry(Descriptor.toJvmName(newName)).getPackageName(); + String oldEntry = deobfReference.entry.getOwnerClassEntry().getPackageName(); + String newEntry = new ClassEntry(newName).getPackageName(); moveClassTree(deobfReference, newName, oldEntry == null, newEntry == null); } public void moveClassTree(EntryReference deobfReference, String newName, boolean isOldOb, boolean isNewOb) { - ClassEntry oldEntry = deobfReference.entry.getClassEntry(); - ClassEntry newEntry = new ClassEntry(Descriptor.toJvmName(newName)); + ClassEntry oldEntry = deobfReference.entry.getOwnerClassEntry(); + ClassEntry newEntry = new ClassEntry(newName); // Ob -> deob if (isOldOb && !isNewOb) { diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index 6d98743..a5528a7 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -51,7 +51,7 @@ public class GuiController { return this.isDirty; } - public void openJar(final JarFile jar) { + public void openJar(final JarFile jar) throws IOException { this.gui.onStartOpenJar(); this.deobfuscator = new Deobfuscator(jar); this.gui.onFinishOpenJar(this.deobfuscator.getJarName()); @@ -161,24 +161,24 @@ public class GuiController { public ClassInheritanceTreeNode getClassInheritance(ClassEntry deobfClassEntry) { ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + ClassInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getClassInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); return ClassInheritanceTreeNode.findNode(rootNode, obfClassEntry); } public ClassImplementationsTreeNode getClassImplementations(ClassEntry deobfClassEntry) { ClassEntry obfClassEntry = this.deobfuscator.obfuscateEntry(deobfClassEntry); - return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfClassEntry); + return this.deobfuscator.getJarIndex().getClassImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfClassEntry); } public MethodInheritanceTreeNode getMethodInheritance(MethodEntry deobfMethodEntry) { MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + MethodInheritanceTreeNode rootNode = this.deobfuscator.getJarIndex().getMethodInheritance(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); return MethodInheritanceTreeNode.findNode(rootNode, obfMethodEntry); } public MethodImplementationsTreeNode getMethodImplementations(MethodEntry deobfMethodEntry) { MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); - List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfMethodEntry); + List rootNodes = this.deobfuscator.getJarIndex().getMethodImplementations(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); if (rootNodes.isEmpty()) { return null; } @@ -190,14 +190,14 @@ public class GuiController { public FieldReferenceTreeNode getFieldReferences(FieldEntry deobfFieldEntry) { FieldEntry obfFieldEntry = this.deobfuscator.obfuscateEntry(deobfFieldEntry); - FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfFieldEntry); + FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfFieldEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } - public BehaviorReferenceTreeNode getMethodReferences(BehaviorEntry deobfBehaviorEntry) { - BehaviorEntry obfBehaviorEntry = this.deobfuscator.obfuscateEntry(deobfBehaviorEntry); - BehaviorReferenceTreeNode rootNode = new BehaviorReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.Deobfuscating), obfBehaviorEntry); + public MethodReferenceTreeNode getMethodReferences(MethodEntry deobfMethodEntry) { + MethodEntry obfMethodEntry = this.deobfuscator.obfuscateEntry(deobfMethodEntry); + MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(this.deobfuscator.getTranslator(TranslationDirection.DEOBFUSCATING), obfMethodEntry); rootNode.load(this.deobfuscator.getJarIndex(), true); return rootNode; } diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java index f80abba..caa985c 100644 --- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java +++ b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java @@ -11,8 +11,6 @@ package cuchaz.enigma.gui.node; -import javassist.bytecode.Descriptor; - import javax.swing.tree.DefaultMutableTreeNode; public class ClassSelectorPackageNode extends DefaultMutableTreeNode { @@ -41,7 +39,7 @@ public class ClassSelectorPackageNode extends DefaultMutableTreeNode { @Override public String toString() { - return !packageName.equals("(none)") ? Descriptor.toJavaName(this.packageName) : "(none)"; + return !packageName.equals("(none)") ? this.packageName : "(none)"; } @Override diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java b/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java deleted file mode 100644 index 9154cc2..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentEntry.java +++ /dev/null @@ -1,110 +0,0 @@ -/******************************************************************************* - * 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.mapping; - -import cuchaz.enigma.utils.Utils; - -public class ArgumentEntry implements Entry { - - private BehaviorEntry behaviorEntry; - private int index; - private String name; - - public ArgumentEntry(BehaviorEntry behaviorEntry, int index, String name) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Argument name cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; - this.index = index; - this.name = name; - } - - public ArgumentEntry(ArgumentEntry other) { - this.behaviorEntry = other.getBehaviorEntry(); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, String newClassName) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(new ClassEntry(newClassName)); - this.index = other.index; - this.name = other.name; - } - - public ArgumentEntry(ArgumentEntry other, BehaviorEntry entry) { - this.behaviorEntry = entry; - this.index = other.index; - this.name = other.name; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public int getIndex() { - return this.index; - } - - @Override - public String getName() { - return this.name; - } - - @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); - } - - @Override - public String getClassName() { - return this.behaviorEntry.getClassName(); - } - - @Override - public ArgumentEntry cloneToNewClass(ClassEntry classEntry) { - return new ArgumentEntry(this, classEntry.getName()); - } - - public String getMethodName() { - return this.behaviorEntry.getName(); - } - - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, Integer.valueOf(this.index).hashCode(), this.name.hashCode()); - } - - @Override - public boolean equals(Object other) { - return other instanceof ArgumentEntry && equals((ArgumentEntry) other); - } - - public boolean equals(ArgumentEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.index == other.index && this.name.equals(other.name); - } - - @Override - public String toString() { - return this.behaviorEntry + "(" + this.index + ":" + this.name + ")"; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java b/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java deleted file mode 100644 index 91ecd10..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ArgumentMapping.java +++ /dev/null @@ -1,50 +0,0 @@ -/******************************************************************************* - * 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.mapping; - -public class ArgumentMapping implements Comparable { - - private int index; - private String name; - - // NOTE: this argument order is important for the MethodReader/MethodWriter - public ArgumentMapping(int index, String name) { - this.index = index; - this.name = NameValidator.validateArgumentName(name); - } - - public ArgumentMapping(ArgumentMapping other) { - this.index = other.index; - this.name = other.name; - } - - public int getIndex() { - return this.index; - } - - public String getName() { - return this.name; - } - - public void setName(String val) { - this.name = NameValidator.validateArgumentName(val); - } - - public ArgumentEntry getObfEntry(BehaviorEntry behaviorEntry) { - return new ArgumentEntry(behaviorEntry, index, name); - } - - @Override - public int compareTo(ArgumentMapping other) { - return Integer.compare(this.index, other.index); - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java b/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java deleted file mode 100644 index 04b4ebc..0000000 --- a/src/main/java/cuchaz/enigma/mapping/BehaviorEntry.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * 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.mapping; - -public interface BehaviorEntry extends Entry { - Signature getSignature(); -} diff --git a/src/main/java/cuchaz/enigma/mapping/ClassDefEntry.java b/src/main/java/cuchaz/enigma/mapping/ClassDefEntry.java new file mode 100644 index 0000000..dc1b02e --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/ClassDefEntry.java @@ -0,0 +1,29 @@ +/******************************************************************************* + * 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.mapping; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; + +public class ClassDefEntry extends ClassEntry { + private final AccessFlags access; + + public ClassDefEntry(String className, AccessFlags access) { + super(className); + Preconditions.checkNotNull(access, "Class access cannot be null"); + this.access = access; + } + + public AccessFlags getAccess() { + return access; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java index 788811f..a49f8dd 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassEntry.java @@ -11,18 +11,18 @@ package cuchaz.enigma.mapping; +import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import java.util.List; public class ClassEntry implements Entry { - private String name; + private final String name; public ClassEntry(String className) { - if (className == null) { - throw new IllegalArgumentException("Class name cannot be null!"); - } + Preconditions.checkNotNull(className, "Class name cannot be null"); + if (className.indexOf('.') >= 0) { throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className); } @@ -49,12 +49,12 @@ public class ClassEntry implements Entry { } @Override - public ClassEntry getClassEntry() { + public ClassEntry getOwnerClassEntry() { return this; } @Override - public ClassEntry cloneToNewClass(ClassEntry classEntry) { + public ClassEntry updateOwnership(ClassEntry classEntry) { return classEntry; } @@ -132,11 +132,7 @@ public class ClassEntry implements Entry { } public String getPackageName() { - int pos = this.name.lastIndexOf('/'); - if (pos > 0) { - return this.name.substring(0, pos); - } - return null; + return getPackageName(this.name); } public String getSimpleName() { @@ -147,6 +143,14 @@ public class ClassEntry implements Entry { return this.name; } + public static String getPackageName(String name) { + int pos = name.lastIndexOf('/'); + if (pos > 0) { + return name.substring(0, pos); + } + return null; + } + public ClassEntry buildClassEntry(List classChain) { assert (classChain.contains(this)); StringBuilder buf = new StringBuilder(); diff --git a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java index 51751ca..c782250 100644 --- a/src/main/java/cuchaz/enigma/mapping/ClassMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/ClassMapping.java @@ -34,7 +34,6 @@ public class ClassMapping implements Comparable { private Map methodsByDeobf; private boolean isDirty; private Mappings.EntryModifier modifier; - private boolean deobfInner; public ClassMapping(String obfFullName) { this(obfFullName, null, Mappings.EntryModifier.UNCHANGED); @@ -81,6 +80,10 @@ public class ClassMapping implements Comparable { return deobfName; } + public String getTranslatedName(TranslationDirection direction) { + return direction.choose(deobfName, obfFullName); + } + //// INNER CLASSES //////// public void setDeobfName(String val) { @@ -191,21 +194,21 @@ public class ClassMapping implements Comparable { return fieldsByObf.values(); } - public boolean containsObfField(String obfName, Type obfType) { - return fieldsByObf.containsKey(getFieldKey(obfName, obfType)); + public boolean containsObfField(String obfName, TypeDescriptor obfDesc) { + return fieldsByObf.containsKey(getFieldKey(obfName, obfDesc)); } - public boolean containsDeobfField(String deobfName, Type deobfType) { - return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfType)); + public boolean containsDeobfField(String deobfName, TypeDescriptor deobfDesc) { + return fieldsByDeobf.containsKey(getFieldKey(deobfName, deobfDesc)); } public void addFieldMapping(FieldMapping fieldMapping) { - String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + String obfKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()); if (fieldsByObf.containsKey(obfKey)) { throw new Error("Already have mapping for " + obfFullName + "." + obfKey); } if (fieldMapping.getDeobfName() != null) { - String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType()); + String deobfKey = getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc()); if (fieldsByDeobf.containsKey(deobfKey)) { throw new Error("Already have mapping for " + deobfName + "." + deobfKey); } @@ -218,63 +221,67 @@ public class ClassMapping implements Comparable { } public void removeFieldMapping(FieldMapping fieldMapping) { - boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType())) != null; + boolean obfWasRemoved = fieldsByObf.remove(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc())) != null; assert (obfWasRemoved); if (fieldMapping.getDeobfName() != null) { - boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfType())) != null; + boolean deobfWasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) != null; assert (deobfWasRemoved); } this.isDirty = true; } - public FieldMapping getFieldByObf(String obfName, Type obfType) { - return fieldsByObf.get(getFieldKey(obfName, obfType)); + public FieldMapping getFieldByObf(String obfName, TypeDescriptor obfDesc) { + return fieldsByObf.get(getFieldKey(obfName, obfDesc)); + } + + public FieldMapping getFieldByObf(FieldEntry field) { + return getFieldByObf(field.getName(), field.getDesc()); } - public FieldMapping getFieldByDeobf(String deobfName, Type obfType) { - return fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + public FieldMapping getFieldByDeobf(String deobfName, TypeDescriptor obfDesc) { + return fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); } - public String getObfFieldName(String deobfName, Type obfType) { - FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfType)); + public String getObfFieldName(String deobfName, TypeDescriptor obfDesc) { + FieldMapping fieldMapping = fieldsByDeobf.get(getFieldKey(deobfName, obfDesc)); if (fieldMapping != null) { return fieldMapping.getObfName(); } return null; } - public String getDeobfFieldName(String obfName, Type obfType) { - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + public String getDeobfFieldName(String obfName, TypeDescriptor obfDesc) { + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); if (fieldMapping != null) { return fieldMapping.getDeobfName(); } return null; } - private String getFieldKey(String name, Type type) { + private String getFieldKey(String name, TypeDescriptor desc) { if (name == null) { throw new IllegalArgumentException("name cannot be null!"); } - if (type == null) { - throw new IllegalArgumentException("type cannot be null!"); + if (desc == null) { + throw new IllegalArgumentException("desc cannot be null!"); } - return name + ":" + type; + return name + ":" + desc; } - public void setFieldName(String obfName, Type obfType, String deobfName) { + public void setFieldName(String obfName, TypeDescriptor obfDesc, String deobfName) { assert (deobfName != null); - FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfType)); + FieldMapping fieldMapping = fieldsByObf.get(getFieldKey(obfName, obfDesc)); if (fieldMapping == null) { - fieldMapping = new FieldMapping(obfName, obfType, deobfName, Mappings.EntryModifier.UNCHANGED); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfType), fieldMapping) == null; + fieldMapping = new FieldMapping(obfName, obfDesc, deobfName, Mappings.EntryModifier.UNCHANGED); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(obfName, obfDesc), fieldMapping) == null; assert (obfWasAdded); } else { - boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfType)) != null; + boolean wasRemoved = fieldsByDeobf.remove(getFieldKey(fieldMapping.getDeobfName(), obfDesc)) != null; assert (wasRemoved); } fieldMapping.setDeobfName(deobfName); if (deobfName != null) { - boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfType), fieldMapping) == null; + boolean wasAdded = fieldsByDeobf.put(getFieldKey(deobfName, obfDesc), fieldMapping) == null; assert (wasAdded); } this.isDirty = true; @@ -282,13 +289,13 @@ public class ClassMapping implements Comparable { //// METHODS //////// - public void setFieldObfNameAndType(String oldObfName, Type obfType, String newObfName, Type newObfType) { + public void setFieldObfNameAndType(String oldObfName, TypeDescriptor obfDesc, String newObfName, TypeDescriptor newObfDesc) { assert (newObfName != null); - FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfType)); + FieldMapping fieldMapping = fieldsByObf.remove(getFieldKey(oldObfName, obfDesc)); assert (fieldMapping != null); fieldMapping.setObfName(newObfName); - fieldMapping.setObfType(newObfType); - boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfType), fieldMapping) == null; + fieldMapping.setObfDesc(newObfDesc); + boolean obfWasAdded = fieldsByObf.put(getFieldKey(newObfName, newObfDesc), fieldMapping) == null; assert (obfWasAdded); this.isDirty = true; } @@ -298,23 +305,23 @@ public class ClassMapping implements Comparable { return methodsByObf.values(); } - public boolean containsObfMethod(String obfName, Signature obfSignature) { - return methodsByObf.containsKey(getMethodKey(obfName, obfSignature)); + public boolean containsObfMethod(String obfName, MethodDescriptor obfDescriptor) { + return methodsByObf.containsKey(getMethodKey(obfName, obfDescriptor)); } - public boolean containsDeobfMethod(String deobfName, Signature obfSignature) { - return methodsByDeobf.containsKey(getMethodKey(deobfName, obfSignature)); + public boolean containsDeobfMethod(String deobfName, MethodDescriptor obfDescriptor) { + return methodsByDeobf.containsKey(getMethodKey(deobfName, obfDescriptor)); } public void addMethodMapping(MethodMapping methodMapping) { - String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + String obfKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); if (methodsByObf.containsKey(obfKey)) { throw new Error("Already have mapping for " + obfFullName + "." + obfKey); } boolean wasAdded = methodsByObf.put(obfKey, methodMapping) == null; assert (wasAdded); if (methodMapping.getDeobfName() != null) { - String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature()); + String deobfKey = getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc()); if (methodsByDeobf.containsKey(deobfKey)) { throw new Error("Already have mapping for " + deobfName + "." + deobfKey); } @@ -326,44 +333,48 @@ public class ClassMapping implements Comparable { } public void removeMethodMapping(MethodMapping methodMapping) { - boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature())) != null; + boolean obfWasRemoved = methodsByObf.remove(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc())) != null; assert (obfWasRemoved); if (methodMapping.getDeobfName() != null) { - boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + boolean deobfWasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; assert (deobfWasRemoved); } this.isDirty = true; } - public MethodMapping getMethodByObf(String obfName, Signature obfSignature) { - return methodsByObf.get(getMethodKey(obfName, obfSignature)); + public MethodMapping getMethodByObf(String obfName, MethodDescriptor obfDescriptor) { + return methodsByObf.get(getMethodKey(obfName, obfDescriptor)); + } + + public MethodMapping getMethodByObf(MethodEntry method) { + return getMethodByObf(method.getName(), method.getDesc()); } - public MethodMapping getMethodByDeobf(String deobfName, Signature obfSignature) { - return methodsByDeobf.get(getMethodKey(deobfName, obfSignature)); + public MethodMapping getMethodByDeobf(String deobfName, MethodDescriptor obfDescriptor) { + return methodsByDeobf.get(getMethodKey(deobfName, obfDescriptor)); } - private String getMethodKey(String name, Signature signature) { + private String getMethodKey(String name, MethodDescriptor descriptor) { if (name == null) { throw new IllegalArgumentException("name cannot be null!"); } - if (signature == null) { - throw new IllegalArgumentException("signature cannot be null!"); + if (descriptor == null) { + throw new IllegalArgumentException("descriptor cannot be null!"); } - return name + signature; + return name + descriptor; } - public void setMethodName(String obfName, Signature obfSignature, String deobfName) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfSignature)); + public void setMethodName(String obfName, MethodDescriptor obfDescriptor, String deobfName) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfName, obfDescriptor)); if (methodMapping == null) { - methodMapping = createMethodMapping(obfName, obfSignature); + methodMapping = createMethodMapping(obfName, obfDescriptor); } else if (methodMapping.getDeobfName() != null) { - boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfSignature())) != null; + boolean wasRemoved = methodsByDeobf.remove(getMethodKey(methodMapping.getDeobfName(), methodMapping.getObfDesc())) != null; assert (wasRemoved); } methodMapping.setDeobfName(deobfName); if (deobfName != null) { - boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfSignature), methodMapping) == null; + boolean wasAdded = methodsByDeobf.put(getMethodKey(deobfName, obfDescriptor), methodMapping) == null; assert (wasAdded); } this.isDirty = true; @@ -371,35 +382,35 @@ public class ClassMapping implements Comparable { //// ARGUMENTS //////// - public void setMethodObfNameAndSignature(String oldObfName, Signature obfSignature, String newObfName, Signature newObfSignature) { + public void setMethodObfNameAndSignature(String oldObfName, MethodDescriptor obfDescriptor, String newObfName, MethodDescriptor newObfDescriptor) { assert (newObfName != null); - MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfSignature)); + MethodMapping methodMapping = methodsByObf.remove(getMethodKey(oldObfName, obfDescriptor)); assert (methodMapping != null); methodMapping.setObfName(newObfName); - methodMapping.setObfSignature(newObfSignature); - boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfSignature), methodMapping) == null; + methodMapping.setObfDescriptor(newObfDescriptor); + boolean obfWasAdded = methodsByObf.put(getMethodKey(newObfName, newObfDescriptor), methodMapping) == null; assert (obfWasAdded); this.isDirty = true; } - public void setArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex, String argumentName) { + public void setArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex, String argumentName) { assert (argumentName != null); - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)); + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)); if (methodMapping == null) { - methodMapping = createMethodMapping(obfMethodName, obfMethodSignature); + methodMapping = createMethodMapping(obfMethodName, obfMethodDescriptor); } - methodMapping.setArgumentName(argumentIndex, argumentName); + methodMapping.setLocalVariableName(argumentIndex, argumentName); this.isDirty = true; } - public void removeArgumentName(String obfMethodName, Signature obfMethodSignature, int argumentIndex) { - methodsByObf.get(getMethodKey(obfMethodName, obfMethodSignature)).removeArgumentName(argumentIndex); + public void removeArgumentName(String obfMethodName, MethodDescriptor obfMethodDescriptor, int argumentIndex) { + methodsByObf.get(getMethodKey(obfMethodName, obfMethodDescriptor)).removeLocalVariableName(argumentIndex); this.isDirty = true; } - private MethodMapping createMethodMapping(String obfName, Signature obfSignature) { - MethodMapping methodMapping = new MethodMapping(obfName, obfSignature); - boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfSignature), methodMapping) == null; + private MethodMapping createMethodMapping(String obfName, MethodDescriptor obfDescriptor) { + MethodMapping methodMapping = new MethodMapping(obfName, obfDescriptor); + boolean wasAdded = methodsByObf.put(getMethodKey(obfName, obfDescriptor), methodMapping) == null; assert (wasAdded); this.isDirty = true; return methodMapping; @@ -459,24 +470,24 @@ public class ClassMapping implements Comparable { // rename field types for (FieldMapping fieldMapping : new ArrayList<>(fieldsByObf.values())) { - String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()); + String oldFieldKey = getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()); if (fieldMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = fieldsByObf.remove(oldFieldKey) != null; assert (wasRemoved); boolean wasAdded = fieldsByObf - .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfType()), fieldMapping) == null; + .put(getFieldKey(fieldMapping.getObfName(), fieldMapping.getObfDesc()), fieldMapping) == null; assert (wasAdded); } } // rename method signatures for (MethodMapping methodMapping : new ArrayList<>(methodsByObf.values())) { - String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()); + String oldMethodKey = getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()); if (methodMapping.renameObfClass(oldObfClassName, newObfClassName)) { boolean wasRemoved = methodsByObf.remove(oldMethodKey) != null; assert (wasRemoved); boolean wasAdded = methodsByObf - .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfSignature()), methodMapping) == null; + .put(getMethodKey(methodMapping.getObfName(), methodMapping.getObfDesc()), methodMapping) == null; assert (wasAdded); } } @@ -490,9 +501,9 @@ public class ClassMapping implements Comparable { return false; } - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfBehaviorEntry.getName(), obfBehaviorEntry.getSignature())); - return methodMapping != null && methodMapping.containsArgument(name); + public boolean containsArgument(MethodEntry obfMethodEntry, String name) { + MethodMapping methodMapping = methodsByObf.get(getMethodKey(obfMethodEntry.getName(), obfMethodEntry.getDesc())); + return methodMapping != null && methodMapping.containsLocalVariable(name); } public ClassEntry getObfEntry() { @@ -521,9 +532,9 @@ public class ClassMapping implements Comparable { this.modifier = modifier; } - public void setFieldModifier(String obfName, Type obfType, Mappings.EntryModifier modifier) { - FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfType), - k -> new FieldMapping(obfName, obfType, null, Mappings.EntryModifier.UNCHANGED)); + public void setFieldModifier(String obfName, TypeDescriptor obfDesc, Mappings.EntryModifier modifier) { + FieldMapping fieldMapping = fieldsByObf.computeIfAbsent(getFieldKey(obfName, obfDesc), + k -> new FieldMapping(obfName, obfDesc, null, Mappings.EntryModifier.UNCHANGED)); if (fieldMapping.getModifier() != modifier) { fieldMapping.setModifier(modifier); @@ -531,7 +542,7 @@ public class ClassMapping implements Comparable { } } - public void setMethodModifier(String obfName, Signature sig, Mappings.EntryModifier modifier) { + public void setMethodModifier(String obfName, MethodDescriptor sig, Mappings.EntryModifier modifier) { MethodMapping methodMapping = methodsByObf.computeIfAbsent(getMethodKey(obfName, sig), k -> new MethodMapping(obfName, sig, null, Mappings.EntryModifier.UNCHANGED)); diff --git a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java b/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java deleted file mode 100644 index 801c410..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ClassNameReplacer.java +++ /dev/null @@ -1,16 +0,0 @@ -/******************************************************************************* - * 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.mapping; - -public interface ClassNameReplacer { - String replace(String className); -} diff --git a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java b/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java deleted file mode 100644 index 20e5113..0000000 --- a/src/main/java/cuchaz/enigma/mapping/ConstructorEntry.java +++ /dev/null @@ -1,105 +0,0 @@ -/******************************************************************************* - * 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.mapping; - -import cuchaz.enigma.utils.Utils; - -public class ConstructorEntry implements BehaviorEntry { - - private ClassEntry classEntry; - private Signature signature; - - public ConstructorEntry(ClassEntry classEntry) { - this(classEntry, null); - } - - public ConstructorEntry(ClassEntry classEntry, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - - this.classEntry = classEntry; - this.signature = signature; - } - - public ConstructorEntry(ConstructorEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.signature = other.signature; - } - - @Override - public ClassEntry getClassEntry() { - return this.classEntry; - } - - @Override - public String getName() { - if (isStatic()) { - return ""; - } - return ""; - } - - public boolean isStatic() { - return this.signature == null; - } - - @Override - public Signature getSignature() { - return this.signature; - } - - @Override - public String getClassName() { - return this.classEntry.getName(); - } - - @Override - public ConstructorEntry cloneToNewClass(ClassEntry classEntry) { - return new ConstructorEntry(this, classEntry.getName()); - } - - @Override - public int hashCode() { - if (isStatic()) { - return Utils.combineHashesOrdered(this.classEntry); - } else { - return Utils.combineHashesOrdered(this.classEntry, this.signature); - } - } - - @Override - public boolean equals(Object other) { - return other instanceof ConstructorEntry && equals((ConstructorEntry) other); - } - - public boolean equals(ConstructorEntry other) { - if (isStatic() != other.isStatic()) { - return false; - } - - if (isStatic()) { - return this.classEntry.equals(other.classEntry); - } else { - return this.classEntry.equals(other.classEntry) && this.signature.equals(other.signature); - } - } - - @Override - public String toString() { - if (isStatic()) { - return this.classEntry.getName() + "." + getName(); - } else { - return this.classEntry.getName() + "." + getName() + this.signature; - } - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java new file mode 100644 index 0000000..1283267 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/DirectionalTranslator.java @@ -0,0 +1,319 @@ +/******************************************************************************* + * 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.mapping; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.bytecode.AccessFlags; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class DirectionalTranslator implements Translator { + + private final TranslationDirection direction; + private final Map classes; + private final TranslationIndex index; + + public DirectionalTranslator(ReferencedEntryPool entryPool) { + this.direction = null; + this.classes = Maps.newHashMap(); + this.index = new TranslationIndex(entryPool); + } + + public DirectionalTranslator(TranslationDirection direction, Map classes, TranslationIndex index) { + this.direction = direction; + this.classes = classes; + this.index = index; + } + + public TranslationDirection getDirection() { + return direction; + } + + public TranslationIndex getTranslationIndex() { + return index; + } + + @Override + public ClassEntry getTranslatedClass(ClassEntry entry) { + String className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); + return new ClassEntry(className); + } + + @Override + public ClassDefEntry getTranslatedClassDef(ClassDefEntry entry) { + String className = entry.isInnerClass() ? translateInnerClassName(entry) : translateClassName(entry); + return new ClassDefEntry(className, getClassModifier(entry).transform(entry.getAccess())); + } + + private String translateClassName(ClassEntry entry) { + // normal classes are easy + ClassMapping classMapping = this.classes.get(entry.getName()); + if (classMapping == null) { + return entry.getName(); + } + return classMapping.getTranslatedName(direction); + } + + private String translateInnerClassName(ClassEntry entry) { + // translate as much of the class chain as we can + List mappingsChain = getClassMappingChain(entry); + String[] obfClassNames = entry.getName().split("\\$"); + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < obfClassNames.length; i++) { + boolean isFirstClass = buf.length() == 0; + String className = null; + ClassMapping classMapping = mappingsChain.get(i); + if (classMapping != null) { + className = this.direction.choose( + classMapping.getDeobfName(), + isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() + ); + } + if (className == null) { + className = obfClassNames[i]; + } + if (!isFirstClass) { + buf.append("$"); + } + buf.append(className); + } + return buf.toString(); + } + + @Override + public FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry) { + String translatedName = translateFieldName(entry); + if (translatedName == null) { + return entry; + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); + AccessFlags translatedAccess = getFieldModifier(entry).transform(entry.getAccess()); + return new FieldDefEntry(translatedOwner, translatedName, translatedDesc, translatedAccess); + } + + @Override + public FieldEntry getTranslatedField(FieldEntry entry) { + String translatedName = translateFieldName(entry); + if (translatedName == null) { + return null; + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + TypeDescriptor translatedDesc = getTranslatedTypeDesc(entry.getDesc()); + return new FieldEntry(translatedOwner, translatedName, translatedDesc); + } + + private String translateFieldName(FieldEntry entry) { + // resolve the class entry + ClassEntry resolvedClassEntry = this.index.resolveEntryOwner(entry); + if (resolvedClassEntry != null) { + // look for the class + ClassMapping classMapping = findClassMapping(resolvedClassEntry); + if (classMapping != null) { + // look for the field + FieldMapping mapping = classMapping.getFieldByObf(entry.getName(), entry.getDesc()); + if (mapping != null) { + return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); + } + } + } + return null; + } + + @Override + public MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry) { + String translatedName = translateMethodName(entry); + if (translatedName == null) { + return entry; + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); + AccessFlags access = getMethodModifier(entry).transform(entry.getAccess()); + return new MethodDefEntry(translatedOwner, translatedName, translatedDesc, access); + } + + @Override + public MethodEntry getTranslatedMethod(MethodEntry entry) { + String translatedName = translateMethodName(entry); + if (translatedName == null) { + return null; + } + ClassEntry translatedOwner = getTranslatedClass(entry.getOwnerClassEntry()); + MethodDescriptor translatedDesc = getTranslatedMethodDesc(entry.getDesc()); + return new MethodEntry(translatedOwner, translatedName, translatedDesc); + } + + private String translateMethodName(MethodEntry entry) { + // resolve the class entry + ClassEntry resolvedOwner = this.index.resolveEntryOwner(entry, true); + if (resolvedOwner != null) { + // look for class + ClassMapping classMapping = findClassMapping(resolvedOwner); + if (classMapping != null) { + // look for the method + MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc()); + if (mapping != null) { + return this.direction.choose(mapping.getDeobfName(), mapping.getObfName()); + } + } + } + return null; + } + + @Override + public LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry) { + String translatedArgumentName = translateLocalVariableName(entry); + if (translatedArgumentName == null) { + translatedArgumentName = inheritLocalVariableName(entry); + } + if (translatedArgumentName == null) { + return null; + } + // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? + MethodEntry translatedOwner = getTranslatedMethod(entry.getOwnerEntry()); + return new LocalVariableEntry(translatedOwner, entry.getIndex(), translatedArgumentName); + } + + @Override + public LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry) { + String translatedArgumentName = translateLocalVariableName(entry); + if (translatedArgumentName == null) { + translatedArgumentName = inheritLocalVariableName(entry); + } + if (translatedArgumentName == null) { + return entry; + } + // TODO: Translating arguments calls method translation.. Can we refactor the code in such a way that we don't need this? + MethodDefEntry translatedOwner = getTranslatedMethodDef(entry.getOwnerEntry()); + TypeDescriptor translatedTypeDesc = getTranslatedTypeDesc(entry.getDesc()); + return new LocalVariableDefEntry(translatedOwner, entry.getIndex(), translatedArgumentName, translatedTypeDesc); + } + + // TODO: support not identical behavior (specific to constructor) + private String translateLocalVariableName(LocalVariableEntry entry) { + // look for identical behavior in superclasses + ClassEntry ownerEntry = entry.getOwnerClassEntry(); + if (ownerEntry != null) { + // look for the class + ClassMapping classMapping = findClassMapping(ownerEntry); + if (classMapping != null) { + // look for the method + MethodMapping methodMapping = classMapping.getMethodByObf(entry.getMethodName(), entry.getMethodDesc()); + if (methodMapping != null) { + int index = entry.getIndex(); + return this.direction.choose( + methodMapping.getDeobfLocalVariableName(index), + methodMapping.getObfLocalVariableName(index) + ); + } + } + } + return null; + } + + private String inheritLocalVariableName(LocalVariableEntry entry) { + List ancestry = this.index.getAncestry(entry.getOwnerClassEntry()); + // Check in mother class for the arg + for (ClassEntry ancestorEntry : ancestry) { + LocalVariableEntry motherArg = entry.updateOwnership(ancestorEntry); + if (this.index.entryExists(motherArg)) { + String result = translateLocalVariableName(motherArg); + if (result != null) { + return result; + } + } + } + return null; + } + + @Override + public TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc) { + return desc.remap(name -> getTranslatedClass(new ClassEntry(name)).getName()); + } + + @Override + public MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor) { + List arguments = descriptor.getArgumentDescs(); + List translatedArguments = new ArrayList<>(arguments.size()); + for (TypeDescriptor argument : arguments) { + translatedArguments.add(getTranslatedTypeDesc(argument)); + } + return new MethodDescriptor(translatedArguments, getTranslatedTypeDesc(descriptor.getReturnDesc())); + } + + private ClassMapping findClassMapping(ClassEntry entry) { + List mappingChain = getClassMappingChain(entry); + return mappingChain.get(mappingChain.size() - 1); + } + + private List getClassMappingChain(ClassEntry entry) { + + // get a list of all the classes in the hierarchy + String[] parts = entry.getName().split("\\$"); + List mappingsChain = Lists.newArrayList(); + + // get mappings for the outer class + ClassMapping outerClassMapping = this.classes.get(parts[0]); + mappingsChain.add(outerClassMapping); + + for (int i = 1; i < parts.length; i++) { + + // get mappings for the inner class + ClassMapping innerClassMapping = null; + if (outerClassMapping != null) { + innerClassMapping = this.direction.choose( + outerClassMapping.getInnerClassByObfSimple(parts[i]), + outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) + ); + } + mappingsChain.add(innerClassMapping); + outerClassMapping = innerClassMapping; + } + + assert (mappingsChain.size() == parts.length); + return mappingsChain; + } + + private Mappings.EntryModifier getClassModifier(ClassEntry entry) { + ClassMapping classMapping = findClassMapping(entry); + if (classMapping != null) { + return classMapping.getModifier(); + } + return Mappings.EntryModifier.UNCHANGED; + } + + private Mappings.EntryModifier getFieldModifier(FieldEntry entry) { + ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); + if (classMapping != null) { + FieldMapping fieldMapping = classMapping.getFieldByObf(entry); + if (fieldMapping != null) { + return fieldMapping.getModifier(); + } + } + return Mappings.EntryModifier.UNCHANGED; + } + + private Mappings.EntryModifier getMethodModifier(MethodEntry entry) { + ClassMapping classMapping = findClassMapping(entry.getOwnerClassEntry()); + if (classMapping != null) { + MethodMapping methodMapping = classMapping.getMethodByObf(entry); + if (methodMapping != null) { + return methodMapping.getModifier(); + } + } + return Mappings.EntryModifier.UNCHANGED; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/Entry.java b/src/main/java/cuchaz/enigma/mapping/Entry.java index c79510b..eb783e9 100644 --- a/src/main/java/cuchaz/enigma/mapping/Entry.java +++ b/src/main/java/cuchaz/enigma/mapping/Entry.java @@ -16,7 +16,7 @@ public interface Entry { String getClassName(); - ClassEntry getClassEntry(); + ClassEntry getOwnerClassEntry(); - Entry cloneToNewClass(ClassEntry classEntry); + Entry updateOwnership(ClassEntry classEntry); } diff --git a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java index 993bb64..c20f6f5 100644 --- a/src/main/java/cuchaz/enigma/mapping/EntryFactory.java +++ b/src/main/java/cuchaz/enigma/mapping/EntryFactory.java @@ -12,19 +12,8 @@ package cuchaz.enigma.mapping; import cuchaz.enigma.analysis.JarIndex; -import javassist.*; -import javassist.bytecode.Descriptor; -import javassist.expr.ConstructorCall; -import javassist.expr.FieldAccess; -import javassist.expr.MethodCall; -import javassist.expr.NewExpr; public class EntryFactory { - - public static ClassEntry getClassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getName())); - } - public static ClassEntry getObfClassEntry(JarIndex jarIndex, ClassMapping classMapping) { ClassEntry obfClassEntry = new ClassEntry(classMapping.getObfFullName()); return obfClassEntry.buildClassEntry(jarIndex.getObfClassChain(obfClassEntry)); @@ -38,95 +27,19 @@ public class EntryFactory { return new ClassEntry(classMapping.getDeobfName()); } - public static ClassEntry getSuperclassEntry(CtClass c) { - return new ClassEntry(Descriptor.toJvmName(c.getClassFile().getSuperclass())); - } - - public static FieldEntry getFieldEntry(CtField field) { - return new FieldEntry(getClassEntry(field.getDeclaringClass()), field.getName(), new Type(field.getFieldInfo().getDescriptor())); - } - - public static FieldEntry getFieldEntry(FieldAccess call) { - return new FieldEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getFieldName(), new Type(call.getSignature())); - } - - public static FieldEntry getFieldEntry(String className, String name, String type) { - return new FieldEntry(new ClassEntry(className), name, new Type(type)); - } - public static FieldEntry getObfFieldEntry(ClassMapping classMapping, FieldMapping fieldMapping) { - return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfType()); - } - - public static MethodEntry getMethodEntry(CtMethod method) { - return new MethodEntry(getClassEntry(method.getDeclaringClass()), method.getName(), new Signature(method.getMethodInfo().getDescriptor())); - } - - public static MethodEntry getMethodEntry(MethodCall call) { - return new MethodEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), call.getMethodName(), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(CtConstructor constructor) { - if (constructor.isClassInitializer()) { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass())); - } else { - return new ConstructorEntry(getClassEntry(constructor.getDeclaringClass()), new Signature(constructor.getMethodInfo().getDescriptor())); - } - } - - public static ConstructorEntry getConstructorEntry(ConstructorCall call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static ConstructorEntry getConstructorEntry(NewExpr call) { - return new ConstructorEntry(new ClassEntry(Descriptor.toJvmName(call.getClassName())), new Signature(call.getSignature())); - } - - public static BehaviorEntry getBehaviorEntry(CtBehavior behavior) { - if (behavior instanceof CtMethod) { - return getMethodEntry((CtMethod) behavior); - } else if (behavior instanceof CtConstructor) { - return getConstructorEntry((CtConstructor) behavior); - } - throw new Error("behavior is neither Method nor Constructor!"); - } - - public static BehaviorEntry getBehaviorEntry(String className, String behaviorName, String behaviorSignature) { - return getBehaviorEntry(new ClassEntry(className), behaviorName, new Signature(behaviorSignature)); - } - - public static BehaviorEntry getBehaviorEntry(String className, String behaviorName) { - return getBehaviorEntry(new ClassEntry(className), behaviorName); - } - - public static BehaviorEntry getBehaviorEntry(String className) { - return new ConstructorEntry(new ClassEntry(className)); - } - - public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName, Signature behaviorSignature) { - switch (behaviorName) { - case "": - return new ConstructorEntry(classEntry, behaviorSignature); - case "": - return new ConstructorEntry(classEntry); - default: - return new MethodEntry(classEntry, behaviorName, behaviorSignature); - } + return new FieldEntry(getObfClassEntry(classMapping), fieldMapping.getObfName(), fieldMapping.getObfDesc()); } - public static BehaviorEntry getBehaviorEntry(ClassEntry classEntry, String behaviorName) { - if (behaviorName.equals("")) { - return new ConstructorEntry(classEntry); - } else { - throw new IllegalArgumentException("Only class initializers don't have signatures"); - } + public static MethodEntry getMethodEntry(ClassEntry classEntry, String name, MethodDescriptor desc) { + return new MethodEntry(classEntry, name, desc); } - public static BehaviorEntry getObfBehaviorEntry(ClassEntry classEntry, MethodMapping methodMapping) { - return getBehaviorEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfSignature()); + public static MethodEntry getObfMethodEntry(ClassEntry classEntry, MethodMapping methodMapping) { + return getMethodEntry(classEntry, methodMapping.getObfName(), methodMapping.getObfDesc()); } - public static BehaviorEntry getObfBehaviorEntry(ClassMapping classMapping, MethodMapping methodMapping) { - return getObfBehaviorEntry(getObfClassEntry(classMapping), methodMapping); + public static MethodEntry getObfMethodEntry(ClassMapping classMapping, MethodMapping methodMapping) { + return getObfMethodEntry(getObfClassEntry(classMapping), methodMapping); } } diff --git a/src/main/java/cuchaz/enigma/mapping/FieldDefEntry.java b/src/main/java/cuchaz/enigma/mapping/FieldDefEntry.java new file mode 100644 index 0000000..262c16c --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/FieldDefEntry.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * 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.mapping; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; + +public class FieldDefEntry extends FieldEntry { + private final AccessFlags access; + + public FieldDefEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc, AccessFlags access) { + super(ownerEntry, name, desc); + Preconditions.checkNotNull(access, "Field access cannot be null"); + this.access = access; + } + + public AccessFlags getAccess() { + return access; + } + + @Override + public FieldDefEntry updateOwnership(ClassEntry owner) { + return new FieldDefEntry(owner, this.name, this.desc, access); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java index 0f1f506..c118ac0 100644 --- a/src/main/java/cuchaz/enigma/mapping/FieldEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/FieldEntry.java @@ -11,40 +11,29 @@ package cuchaz.enigma.mapping; +import com.google.common.base.Preconditions; import cuchaz.enigma.utils.Utils; public class FieldEntry implements Entry { - private ClassEntry classEntry; - private String name; - private Type type; + protected final ClassEntry ownerEntry; + protected final String name; + protected final TypeDescriptor desc; // NOTE: this argument order is important for the MethodReader/MethodWriter - public FieldEntry(ClassEntry classEntry, String name, Type type) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Field name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Field type cannot be null!"); - } + public FieldEntry(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + Preconditions.checkNotNull(ownerEntry, "Owner cannot be null"); + Preconditions.checkNotNull(name, "Field name cannot be null"); + Preconditions.checkNotNull(desc, "Field descriptor cannot be null"); - this.classEntry = classEntry; + this.ownerEntry = ownerEntry; this.name = name; - this.type = type; - } - - public FieldEntry(FieldEntry other, ClassEntry newClassEntry) { - this.classEntry = newClassEntry; - this.name = other.name; - this.type = other.type; + this.desc = desc; } @Override - public ClassEntry getClassEntry() { - return this.classEntry; + public ClassEntry getOwnerClassEntry() { + return this.ownerEntry; } @Override @@ -54,21 +43,21 @@ public class FieldEntry implements Entry { @Override public String getClassName() { - return this.classEntry.getName(); + return this.ownerEntry.getName(); } - public Type getType() { - return this.type; + public TypeDescriptor getDesc() { + return this.desc; } @Override - public FieldEntry cloneToNewClass(ClassEntry classEntry) { - return new FieldEntry(this, classEntry); + public FieldEntry updateOwnership(ClassEntry owner) { + return new FieldEntry(owner, this.name, this.desc); } @Override public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.type); + return Utils.combineHashesOrdered(this.ownerEntry, this.name, this.desc); } @Override @@ -77,11 +66,11 @@ public class FieldEntry implements Entry { } public boolean equals(FieldEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.type.equals(other.type); + return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.desc.equals(other.desc); } @Override public String toString() { - return this.classEntry.getName() + "." + this.name + ":" + this.type; + return this.ownerEntry.getName() + "." + this.name + ":" + this.desc; } } diff --git a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java index cd761b4..3c46a37 100644 --- a/src/main/java/cuchaz/enigma/mapping/FieldMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/FieldMapping.java @@ -17,26 +17,19 @@ public class FieldMapping implements Comparable, MemberMapping, MemberMapping, MemberMapping - { + // rename obf classes in the desc + TypeDescriptor newDesc = this.obfDesc.remap(className -> { if (className.equals(oldObfClassName)) { return newObfClassName; } - return null; + return className; }); - if (!newType.equals(this.obfType)) { - this.obfType = newType; + if (!newDesc.equals(this.obfDesc)) { + this.obfDesc = newDesc; return true; } return false; diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableDefEntry.java new file mode 100644 index 0000000..cc677c5 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/LocalVariableDefEntry.java @@ -0,0 +1,75 @@ +package cuchaz.enigma.mapping; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.utils.Utils; + +/** + * TypeDescriptor... + * Created by Thog + * 19/10/2016 + */ +public class LocalVariableDefEntry extends LocalVariableEntry { + + protected final MethodDefEntry ownerEntry; + protected final TypeDescriptor desc; + + public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name, TypeDescriptor desc) { + super(ownerEntry, index, name); + Preconditions.checkNotNull(desc, "Variable desc cannot be null"); + + this.ownerEntry = ownerEntry; + this.desc = desc; + } + + public LocalVariableDefEntry(MethodDefEntry ownerEntry, int index, String name) { + super(ownerEntry, index, name); + + this.ownerEntry = ownerEntry; + + int namedIndex = getNamedIndex(); + if (namedIndex < 0) { + this.desc = TypeDescriptor.of(ownerEntry.getOwnerClassEntry().getName()); + } else { + this.desc = ownerEntry.getDesc().getArgumentDescs().get(namedIndex); + } + } + + @Override + public MethodDefEntry getOwnerEntry() { + return this.ownerEntry; + } + + public TypeDescriptor getDesc() { + return desc; + } + + public int getNamedIndex() { + // If we're not static, "this" is bound to index 0 + int indexOffset = ownerEntry.getAccess().isStatic() ? 0 : 1; + return index - indexOffset; + } + + @Override + public LocalVariableDefEntry updateOwnership(ClassEntry classEntry) { + return new LocalVariableDefEntry(ownerEntry.updateOwnership(classEntry), index, name, desc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.ownerEntry, this.desc.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); + } + + @Override + public boolean equals(Object other) { + return other instanceof LocalVariableDefEntry && equals((LocalVariableDefEntry) other); + } + + public boolean equals(LocalVariableDefEntry other) { + return this.ownerEntry.equals(other.ownerEntry) && this.desc.equals(other.desc) && this.name.equals(other.name) && this.index == other.index; + } + + @Override + public String toString() { + return this.ownerEntry + "(" + this.index + ":" + this.name + ":" + this.desc + ")"; + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java index 2bb5e3f..dcfd0ff 100644 --- a/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/LocalVariableEntry.java @@ -1,52 +1,31 @@ package cuchaz.enigma.mapping; +import com.google.common.base.Preconditions; import cuchaz.enigma.utils.Utils; /** - * Desc... + * TypeDescriptor... * Created by Thog * 19/10/2016 */ public class LocalVariableEntry implements Entry { - protected final BehaviorEntry behaviorEntry; + protected final MethodEntry ownerEntry; protected final String name; - protected final Type type; protected final int index; - public LocalVariableEntry(BehaviorEntry behaviorEntry, int index, String name, Type type) { - if (behaviorEntry == null) { - throw new IllegalArgumentException("Behavior cannot be null!"); - } - if (index < 0) { - throw new IllegalArgumentException("Index must be non-negative!"); - } - if (name == null) { - throw new IllegalArgumentException("Variable name cannot be null!"); - } - if (type == null) { - throw new IllegalArgumentException("Variable type cannot be null!"); - } - - this.behaviorEntry = behaviorEntry; + public LocalVariableEntry(MethodEntry ownerEntry, int index, String name) { + Preconditions.checkNotNull(ownerEntry, "Variable owner cannot be null"); + Preconditions.checkNotNull(name, "Variable name cannot be null"); + Preconditions.checkArgument(index >= 0, "Index must be positive"); + + this.ownerEntry = ownerEntry; this.name = name; - this.type = type; this.index = index; } - public LocalVariableEntry(LocalVariableEntry other, ClassEntry newClassEntry) { - this.behaviorEntry = (BehaviorEntry) other.behaviorEntry.cloneToNewClass(newClassEntry); - this.name = other.name; - this.type = other.type; - this.index = other.index; - } - - public BehaviorEntry getBehaviorEntry() { - return this.behaviorEntry; - } - - public Type getType() { - return type; + public MethodEntry getOwnerEntry() { + return this.ownerEntry; } public int getIndex() { @@ -59,31 +38,31 @@ public class LocalVariableEntry implements Entry { } @Override - public ClassEntry getClassEntry() { - return this.behaviorEntry.getClassEntry(); + public ClassEntry getOwnerClassEntry() { + return this.ownerEntry.getOwnerClassEntry(); } @Override public String getClassName() { - return this.behaviorEntry.getClassName(); + return this.ownerEntry.getClassName(); } @Override - public LocalVariableEntry cloneToNewClass(ClassEntry classEntry) { - return new LocalVariableEntry(this, classEntry); + public LocalVariableEntry updateOwnership(ClassEntry classEntry) { + return new LocalVariableEntry(ownerEntry.updateOwnership(classEntry), index, name); } public String getMethodName() { - return this.behaviorEntry.getName(); + return this.ownerEntry.getName(); } - public Signature getMethodSignature() { - return this.behaviorEntry.getSignature(); + public MethodDescriptor getMethodDesc() { + return this.ownerEntry.getDesc(); } @Override public int hashCode() { - return Utils.combineHashesOrdered(this.behaviorEntry, this.type.hashCode(), this.name.hashCode(), Integer.hashCode(this.index)); + return Utils.combineHashesOrdered(this.ownerEntry, this.name.hashCode(), Integer.hashCode(this.index)); } @Override @@ -92,11 +71,11 @@ public class LocalVariableEntry implements Entry { } public boolean equals(LocalVariableEntry other) { - return this.behaviorEntry.equals(other.behaviorEntry) && this.type.equals(other.type) && this.name.equals(other.name) && this.index == other.index; + return this.ownerEntry.equals(other.ownerEntry) && this.name.equals(other.name) && this.index == other.index; } @Override public String toString() { - return this.behaviorEntry + "(" + this.index + ":" + this.name + ":" + this.type + ")"; + return this.ownerEntry + "(" + this.index + ":" + this.name + ")"; } } diff --git a/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java new file mode 100644 index 0000000..193c566 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/LocalVariableMapping.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * 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.mapping; + +public class LocalVariableMapping implements Comparable { + + private int index; + private String name; + + // NOTE: this argument order is important for the MethodReader/MethodWriter + public LocalVariableMapping(int index, String name) { + this.index = index; + this.name = NameValidator.validateArgumentName(name); + } + + public LocalVariableMapping(LocalVariableMapping other) { + this.index = other.index; + this.name = other.name; + } + + public int getIndex() { + return this.index; + } + + public String getName() { + return this.name; + } + + public void setName(String val) { + this.name = NameValidator.validateArgumentName(val); + } + + public LocalVariableEntry getObfEntry(MethodEntry methodEntry) { + return new LocalVariableEntry(methodEntry, index, name); + } + + @Override + public int compareTo(LocalVariableMapping other) { + return Integer.compare(this.index, other.index); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/Mappings.java b/src/main/java/cuchaz/enigma/mapping/Mappings.java index cf78ca3..cc1ec9c 100644 --- a/src/main/java/cuchaz/enigma/mapping/Mappings.java +++ b/src/main/java/cuchaz/enigma/mapping/Mappings.java @@ -15,6 +15,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import cuchaz.enigma.analysis.TranslationIndex; +import cuchaz.enigma.bytecode.AccessFlags; import cuchaz.enigma.throwables.MappingConflict; import java.io.File; @@ -96,11 +97,11 @@ public class Mappings { public Translator getTranslator(TranslationDirection direction, TranslationIndex index) { switch (direction) { - case Deobfuscating: + case DEOBFUSCATING: - return new Translator(direction, this.classesByObf, index); + return new DirectionalTranslator(direction, this.classesByObf, index); - case Obfuscating: + case OBFUSCATING: // fill in the missing deobf class entries with obf entries Map classes = Maps.newHashMap(); @@ -114,9 +115,9 @@ public class Mappings { // translate the translation index // NOTE: this isn't actually recursive - TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.Deobfuscating, index)); + TranslationIndex deobfIndex = new TranslationIndex(index, getTranslator(TranslationDirection.DEOBFUSCATING, index)); - return new Translator(direction, classes, deobfIndex); + return new DirectionalTranslator(direction, classes, deobfIndex); default: throw new Error("Invalid translation direction!"); @@ -151,9 +152,9 @@ public class Mappings { // add classes from method signatures for (MethodMapping methodMapping : classMapping.methods()) { - for (Type type : methodMapping.getObfSignature().types()) { - if (type.hasClass()) { - classNames.add(type.getClassEntry().getClassName()); + for (TypeDescriptor desc : methodMapping.getObfDesc().types()) { + if (desc.containsType()) { + classNames.add(desc.getOwnerEntry().getClassName()); } } } @@ -165,9 +166,9 @@ public class Mappings { return this.classesByDeobf.containsKey(deobfName); } - public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, Type obfType) { + public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName, TypeDescriptor obfDesc) { ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfField(deobfName, obfType); + return classMapping != null && classMapping.containsDeobfField(deobfName, obfDesc); } public boolean containsDeobfField(ClassEntry obfClassEntry, String deobfName) { @@ -180,14 +181,14 @@ public class Mappings { return false; } - public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, Signature obfSignature) { + public boolean containsDeobfMethod(ClassEntry obfClassEntry, String deobfName, MethodDescriptor obfDescriptor) { ClassMapping classMapping = this.classesByObf.get(obfClassEntry.getName()); - return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfSignature); + return classMapping != null && classMapping.containsDeobfMethod(deobfName, obfDescriptor); } - public boolean containsArgument(BehaviorEntry obfBehaviorEntry, String name) { - ClassMapping classMapping = this.classesByObf.get(obfBehaviorEntry.getClassName()); - return classMapping != null && classMapping.containsArgument(obfBehaviorEntry, name); + public boolean containsArgument(MethodEntry obfMethodEntry, String name) { + ClassMapping classMapping = this.classesByObf.get(obfMethodEntry.getClassName()); + return classMapping != null && classMapping.containsArgument(obfMethodEntry, name); } public List getClassMappingChain(ClassEntry obfClass) { @@ -239,5 +240,19 @@ public class Mappings { public String getFormattedName() { return " ACC:" + super.toString(); } + + public AccessFlags transform(AccessFlags access) { + switch (this) { + case PUBLIC: + return access.setPublic(); + case PROTECTED: + return access.setProtected(); + case PRIVATE: + return access.setPrivate(); + case UNCHANGED: + default: + return access; + } + } } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java index 172641b..4d5be2f 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsChecker.java @@ -23,7 +23,7 @@ public class MappingsChecker { private Map droppedClassMappings; private Map droppedInnerClassMappings; private Map droppedFieldMappings; - private Map droppedMethodMappings; + private Map droppedMethodMappings; public MappingsChecker(JarIndex index) { this.index = index; @@ -45,7 +45,7 @@ public class MappingsChecker { return this.droppedFieldMappings; } - public Map getDroppedMethodMappings() { + public Map getDroppedMethodMappings() { return this.droppedMethodMappings; } @@ -77,10 +77,10 @@ public class MappingsChecker { // check methods for (MethodMapping methodMapping : Lists.newArrayList(classMapping.methods())) { - BehaviorEntry obfBehaviorEntry = EntryFactory.getObfBehaviorEntry(classEntry, methodMapping); - if (!this.index.containsObfBehavior(obfBehaviorEntry)) { + MethodEntry obfMethodEntry = EntryFactory.getObfMethodEntry(classEntry, methodMapping); + if (!this.index.containsObfMethod(obfMethodEntry)) { classMapping.removeMethodMapping(methodMapping); - this.droppedMethodMappings.put(obfBehaviorEntry, methodMapping); + this.droppedMethodMappings.put(obfMethodEntry, methodMapping); } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java index a0d4313..d1d5634 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaReader.java @@ -123,8 +123,8 @@ public class MappingsEnigmaReader { return mappings; } - private ArgumentMapping readArgument(String[] parts) { - return new ArgumentMapping(Integer.parseInt(parts[1]), parts[2]); + private LocalVariableMapping readArgument(String[] parts) { + return new LocalVariableMapping(Integer.parseInt(parts[1]), parts[2]); } private ClassMapping readClass(String[] parts, boolean makeSimple) { @@ -150,27 +150,27 @@ public class MappingsEnigmaReader { if (parts.length == 4) { boolean access = parts[3].startsWith("ACC:"); if (access) - mapping = new FieldMapping(parts[1], new Type(parts[2]), null, + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); else - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.UNCHANGED); } else if (parts.length == 5) - mapping = new FieldMapping(parts[1], new Type(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); + mapping = new FieldMapping(parts[1], new TypeDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); return mapping; } private MethodMapping readMethod(String[] parts) { MethodMapping mapping = null; if (parts.length == 3) - mapping = new MethodMapping(parts[1], new Signature(parts[2])); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2])); else if (parts.length == 4) { boolean access = parts[3].startsWith("ACC:"); if (access) - mapping = new MethodMapping(parts[1], new Signature(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[2]), null, Mappings.EntryModifier.valueOf(parts[3].substring(4))); else - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2]); + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2]); } else if (parts.length == 5) - mapping = new MethodMapping(parts[1], new Signature(parts[3]), parts[2], + mapping = new MethodMapping(parts[1], new MethodDescriptor(parts[3]), parts[2], Mappings.EntryModifier.valueOf(parts[4].substring(4))); return mapping; } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java index ba1b258..1929977 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsEnigmaWriter.java @@ -127,29 +127,29 @@ public class MappingsEnigmaWriter { private void write(PrintWriter out, FieldMapping fieldMapping, int depth) { if (fieldMapping.getDeobfName() == null) - out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfType().toString(), + out.format("%sFIELD %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getObfDesc().toString(), fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); else - out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfType().toString(), + out.format("%sFIELD %s %s %s%s\n", getIndent(depth), fieldMapping.getObfName(), fieldMapping.getDeobfName(), fieldMapping.getObfDesc().toString(), fieldMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : fieldMapping.getModifier().getFormattedName()); } private void write(PrintWriter out, MethodMapping methodMapping, int depth) throws IOException { if (methodMapping.getDeobfName() == null) { - out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfSignature(), + out.format("%sMETHOD %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getObfDesc(), methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } else { - out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfSignature(), + out.format("%sMETHOD %s %s %s%s\n", getIndent(depth), methodMapping.getObfName(), methodMapping.getDeobfName(), methodMapping.getObfDesc(), methodMapping.getModifier() == Mappings.EntryModifier.UNCHANGED ? "" : methodMapping.getModifier().getFormattedName()); } - for (ArgumentMapping argumentMapping : sorted(methodMapping.arguments())) { - write(out, argumentMapping, depth + 1); + for (LocalVariableMapping localVariableMapping : sorted(methodMapping.arguments())) { + write(out, localVariableMapping, depth + 1); } } - private void write(PrintWriter out, ArgumentMapping argumentMapping, int depth) { - out.format("%sARG %d %s\n", getIndent(depth), argumentMapping.getIndex(), argumentMapping.getName()); + private void write(PrintWriter out, LocalVariableMapping localVariableMapping, int depth) { + out.format("%sARG %d %s\n", getIndent(depth), localVariableMapping.getIndex(), localVariableMapping.getName()); } private > List sorted(Iterable classes) { diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java index 7126d2b..e215a0f 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsRenamer.java @@ -25,12 +25,14 @@ import java.util.zip.GZIPOutputStream; public class MappingsRenamer { - private JarIndex index; + private final JarIndex index; + private final ReferencedEntryPool entryPool; private Mappings mappings; - public MappingsRenamer(JarIndex index, Mappings mappings) { + public MappingsRenamer(JarIndex index, Mappings mappings, ReferencedEntryPool entryPool) { this.index = index; this.mappings = mappings; + this.entryPool = entryPool; } public void setMappings(Mappings mappings) { @@ -46,7 +48,7 @@ public class MappingsRenamer { if (deobfName != null) { // make sure we don't rename to an existing obf or deobf class - if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(new ClassEntry(deobfName))) { + if (mappings.containsDeobfClass(deobfName) || index.containsObfClass(entryPool.getClass(deobfName))) { throw new IllegalNameException(deobfName, "There is already a class with that name"); } } @@ -87,13 +89,13 @@ public class MappingsRenamer { public void setFieldName(FieldEntry obf, String deobfName) { deobfName = NameValidator.validateFieldName(deobfName); - FieldEntry targetEntry = new FieldEntry(obf.getClassEntry(), deobfName, obf.getType()); + FieldEntry targetEntry = entryPool.getField(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); ClassEntry definedClass = null; - if (mappings.containsDeobfField(obf.getClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) - definedClass = obf.getClassEntry(); + if (mappings.containsDeobfField(obf.getOwnerClassEntry(), deobfName) || index.containsEntryWithSameName(targetEntry)) + definedClass = obf.getOwnerClassEntry(); else { - for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getClassEntry())) { - if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.cloneToNewClass(ancestorEntry))) { + for (ClassEntry ancestorEntry : this.index.getTranslationIndex().getAncestry(obf.getOwnerClassEntry())) { + if (mappings.containsDeobfField(ancestorEntry, deobfName) || index.containsEntryWithSameName(targetEntry.updateOwnership(ancestorEntry))) { definedClass = ancestorEntry; break; } @@ -101,42 +103,44 @@ public class MappingsRenamer { } if (definedClass != null) { - String className = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(definedClass.getClassName()); + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String className = translator.getTranslatedClass(entryPool.getClass(definedClass.getClassName())).getName(); if (className == null) className = definedClass.getClassName(); throw new IllegalNameException(deobfName, "There is already a field with that name in " + className); } - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), deobfName); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getDesc(), deobfName); } public void removeFieldMapping(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getType())); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.removeFieldMapping(classMapping.getFieldByObf(obf.getName(), obf.getDesc())); } public void markFieldAsDeobfuscated(FieldEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setFieldName(obf.getName(), obf.getType(), obf.getName()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setFieldName(obf.getName(), obf.getDesc(), obf.getName()); } private void validateMethodTreeName(MethodEntry entry, String deobfName) { - MethodEntry targetEntry = new MethodEntry(entry.getClassEntry(), deobfName, entry.getSignature()); + MethodEntry targetEntry = entryPool.getMethod(entry.getOwnerClassEntry(), deobfName, entry.getDesc()); // TODO: Verify if I don't break things - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); - if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getSignature()) && classMapping.getMethodByObf(entry.getName(), entry.getSignature()) != classMapping.getMethodByDeobf(deobfName, entry.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(entry.getClassName()); + ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); + if ((classMapping != null && classMapping.containsDeobfMethod(deobfName, entry.getDesc()) && classMapping.getMethodByObf(entry.getName(), entry.getDesc()) != classMapping.getMethodByDeobf(deobfName, entry.getDesc())) + || index.containsObfMethod(targetEntry)) { + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String deobfClassName = translator.getTranslatedClass(entryPool.getClass(entry.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = entry.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } - for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getClassEntry())) { - validateMethodTreeName(entry.cloneToNewClass(child), deobfName); + for (ClassEntry child : index.getTranslationIndex().getSubclass(entry.getOwnerClassEntry())) { + validateMethodTreeName(entry.updateOwnership(child), deobfName); } } @@ -155,20 +159,21 @@ public class MappingsRenamer { public void setMethodName(MethodEntry obf, String deobfName) { deobfName = NameValidator.validateMethodName(deobfName); - MethodEntry targetEntry = new MethodEntry(obf.getClassEntry(), deobfName, obf.getSignature()); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); + MethodEntry targetEntry = entryPool.getMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); // TODO: Verify if I don't break things - if ((mappings.containsDeobfMethod(obf.getClassEntry(), deobfName, obf.getSignature()) && classMapping.getMethodByObf(obf.getName(), obf.getSignature()) != classMapping.getMethodByDeobf(deobfName, obf.getSignature())) - || index.containsObfBehavior(targetEntry)) { - String deobfClassName = mappings.getTranslator(TranslationDirection.Deobfuscating, index.getTranslationIndex()).translateClass(obf.getClassName()); + if ((mappings.containsDeobfMethod(obf.getOwnerClassEntry(), deobfName, obf.getDesc()) && classMapping.getMethodByObf(obf.getName(), obf.getDesc()) != classMapping.getMethodByDeobf(deobfName, obf.getDesc())) + || index.containsObfMethod(targetEntry)) { + Translator translator = mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index.getTranslationIndex()); + String deobfClassName = translator.getTranslatedClass(entryPool.getClass(obf.getClassName())).getClassName(); if (deobfClassName == null) { deobfClassName = obf.getClassName(); } throw new IllegalNameException(deobfName, "There is already a method with that name and signature in class " + deobfClassName); } - classMapping.setMethodName(obf.getName(), obf.getSignature(), deobfName); + classMapping.setMethodName(obf.getName(), obf.getDesc(), deobfName); } public void removeMethodTreeMapping(MethodEntry obf) { @@ -176,8 +181,8 @@ public class MappingsRenamer { } public void removeMethodMapping(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), null); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getDesc(), null); } public void markMethodTreeAsDeobfuscated(MethodEntry obf) { @@ -185,30 +190,25 @@ public class MappingsRenamer { } public void markMethodAsDeobfuscated(MethodEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setMethodName(obf.getName(), obf.getSignature(), obf.getName()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setMethodName(obf.getName(), obf.getDesc(), obf.getName()); } - public void setArgumentTreeName(ArgumentEntry obf, String deobfName) { - if (!(obf.getBehaviorEntry() instanceof MethodEntry)) { - setArgumentName(obf, deobfName); - return; - } - - MethodEntry obfMethod = (MethodEntry) obf.getBehaviorEntry(); + public void setLocalVariableTreeName(LocalVariableEntry obf, String deobfName) { + MethodEntry obfMethod = obf.getOwnerEntry(); Set implementations = index.getRelatedMethodImplementations(obfMethod); for (MethodEntry entry : implementations) { - ClassMapping classMapping = mappings.getClassByObf(entry.getClassEntry()); + ClassMapping classMapping = mappings.getClassByObf(entry.getOwnerClassEntry()); if (classMapping != null) { - MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getSignature()); + MethodMapping mapping = classMapping.getMethodByObf(entry.getName(), entry.getDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { + for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { + if (localVariableMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) + || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } @@ -218,45 +218,45 @@ public class MappingsRenamer { } for (MethodEntry entry : implementations) { - setArgumentName(new ArgumentEntry(obf, entry), deobfName); + setLocalVariableName(new LocalVariableEntry(entry, obf.getIndex(), obf.getName()), deobfName); } } - public void setArgumentName(ArgumentEntry obf, String deobfName) { + public void setLocalVariableName(LocalVariableEntry obf, String deobfName) { deobfName = NameValidator.validateArgumentName(deobfName); - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodSignature()); + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + MethodMapping mapping = classMapping.getMethodByObf(obf.getMethodName(), obf.getMethodDesc()); // NOTE: don't need to check arguments for name collisions with names determined by Procyon // TODO: Verify if I don't break things if (mapping != null) { - for (ArgumentMapping argumentMapping : Lists.newArrayList(mapping.arguments())) { - if (argumentMapping.getIndex() != obf.getIndex()) { - if (mapping.getDeobfArgumentName(argumentMapping.getIndex()).equals(deobfName) - || argumentMapping.getName().equals(deobfName)) { + for (LocalVariableMapping localVariableMapping : Lists.newArrayList(mapping.arguments())) { + if (localVariableMapping.getIndex() != obf.getIndex()) { + if (mapping.getDeobfLocalVariableName(localVariableMapping.getIndex()).equals(deobfName) + || localVariableMapping.getName().equals(deobfName)) { throw new IllegalNameException(deobfName, "There is already an argument with that name"); } } } } - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), deobfName); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), deobfName); } - public void removeArgumentMapping(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex()); + public void removeLocalVariableMapping(LocalVariableEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.removeArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex()); } - public void markArgumentAsDeobfuscated(ArgumentEntry obf) { - ClassMapping classMapping = getOrCreateClassMapping(obf.getClassEntry()); - classMapping.setArgumentName(obf.getMethodName(), obf.getMethodSignature(), obf.getIndex(), obf.getName()); + public void markArgumentAsDeobfuscated(LocalVariableEntry obf) { + ClassMapping classMapping = getOrCreateClassMapping(obf.getOwnerClassEntry()); + classMapping.setArgumentName(obf.getMethodName(), obf.getMethodDesc(), obf.getIndex(), obf.getName()); } public boolean moveFieldToObfClass(ClassMapping classMapping, FieldMapping fieldMapping, ClassEntry obfClass) { classMapping.removeFieldMapping(fieldMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfType())) { - if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfType())) { + if (!targetClassMapping.containsObfField(fieldMapping.getObfName(), fieldMapping.getObfDesc())) { + if (!targetClassMapping.containsDeobfField(fieldMapping.getDeobfName(), fieldMapping.getObfDesc())) { targetClassMapping.addFieldMapping(fieldMapping); return true; } else { @@ -269,12 +269,12 @@ public class MappingsRenamer { public boolean moveMethodToObfClass(ClassMapping classMapping, MethodMapping methodMapping, ClassEntry obfClass) { classMapping.removeMethodMapping(methodMapping); ClassMapping targetClassMapping = getOrCreateClassMapping(obfClass); - if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfSignature())) { - if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfSignature())) { + if (!targetClassMapping.containsObfMethod(methodMapping.getObfName(), methodMapping.getObfDesc())) { + if (!targetClassMapping.containsDeobfMethod(methodMapping.getDeobfName(), methodMapping.getObfDesc())) { targetClassMapping.addMethodMapping(methodMapping); return true; } else { - System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfSignature()); + System.err.println("WARNING: deobf method was already there: " + obfClass + "." + methodMapping.getDeobfName() + methodMapping.getObfDesc()); } } return false; @@ -326,12 +326,35 @@ public class MappingsRenamer { } public void setFieldModifier(FieldEntry obEntry, Mappings.EntryModifier modifier) { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setFieldModifier(obEntry.getName(), obEntry.getType(), modifier); + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); + classMapping.setFieldModifier(obEntry.getName(), obEntry.getDesc(), modifier); + } + + public void setMethodModifier(MethodEntry obEntry, Mappings.EntryModifier modifier) { + ClassMapping classMapping = getOrCreateClassMapping(obEntry.getOwnerClassEntry()); + classMapping.setMethodModifier(obEntry.getName(), obEntry.getDesc(), modifier); + } + + public Mappings.EntryModifier getClassModifier(ClassEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry); + return classMapping.getModifier(); } - public void setMethodModifier(BehaviorEntry obEntry, Mappings.EntryModifier modifier) { - ClassMapping classMapping = getOrCreateClassMapping(obEntry.getClassEntry()); - classMapping.setMethodModifier(obEntry.getName(), obEntry.getSignature(), modifier); + public Mappings.EntryModifier getFieldModifier(FieldEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); + FieldMapping fieldMapping = classMapping.getFieldByObf(obfEntry); + if (fieldMapping == null) { + return Mappings.EntryModifier.UNCHANGED; + } + return fieldMapping.getModifier(); + } + + public Mappings.EntryModifier getMethodModfifier(MethodEntry obfEntry) { + ClassMapping classMapping = getOrCreateClassMapping(obfEntry.getOwnerClassEntry()); + MethodMapping methodMapping = classMapping.getMethodByObf(obfEntry); + if (methodMapping == null) { + return Mappings.EntryModifier.UNCHANGED; + } + return methodMapping.getModifier(); } } diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java index b0eb826..95daa73 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsSRGWriter.java @@ -19,7 +19,7 @@ public class MappingsSRGWriter { } file.createNewFile(); - TranslationIndex index = new TranslationIndex(); + TranslationIndex index = new TranslationIndex(new ReferencedEntryPool()); PrintWriter writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(file), Charsets.UTF_8)); List fieldMappings = new ArrayList<>(); @@ -43,7 +43,7 @@ public class MappingsSRGWriter { } for (MethodMapping methodMapping : sorted(innerClassMapping.methods())) { - methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + methodMappings.add("MD: " + innerClassName + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + innerDeobfClassName + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); } } @@ -52,7 +52,7 @@ public class MappingsSRGWriter { } for (MethodMapping methodMapping : sorted(classMapping.methods())) { - methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfSignature() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.Deobfuscating, index).translateSignature(methodMapping.getObfSignature())); + methodMappings.add("MD: " + classMapping.getObfFullName() + "/" + methodMapping.getObfName() + " " + methodMapping.getObfDesc() + " " + classMapping.getDeobfName() + "/" + methodMapping.getDeobfName() + " " + mappings.getTranslator(TranslationDirection.DEOBFUSCATING, index).getTranslatedMethodDesc(methodMapping.getObfDesc())); } } for (String fd : fieldMappings) { diff --git a/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java b/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java index dfe9e88..e635fa1 100644 --- a/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java +++ b/src/main/java/cuchaz/enigma/mapping/MappingsTinyReader.java @@ -20,11 +20,11 @@ public class MappingsTinyReader { } public FieldMapping readField(String[] parts) { - return new FieldMapping(parts[3], new Type(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); + return new FieldMapping(parts[3], new TypeDescriptor(parts[2]), parts[4], Mappings.EntryModifier.UNCHANGED); } public MethodMapping readMethod(String[] parts) { - return new MethodMapping(parts[3], new Signature(parts[2]), parts[4]); + return new MethodMapping(parts[3], new MethodDescriptor(parts[2]), parts[4]); } public Mappings read(File file) throws IOException, MappingParseException { diff --git a/src/main/java/cuchaz/enigma/mapping/MethodDefEntry.java b/src/main/java/cuchaz/enigma/mapping/MethodDefEntry.java new file mode 100644 index 0000000..d6a160d --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/MethodDefEntry.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * 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.mapping; + +import com.google.common.base.Preconditions; +import cuchaz.enigma.bytecode.AccessFlags; + +public class MethodDefEntry extends MethodEntry { + + private final AccessFlags access; + + public MethodDefEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor, AccessFlags access) { + super(classEntry, name, descriptor); + Preconditions.checkNotNull(access, "Method access cannot be null"); + this.access = access; + } + + public AccessFlags getAccess() { + return access; + } + + @Override + public MethodDefEntry updateOwnership(ClassEntry classEntry) { + return new MethodDefEntry(new ClassEntry(classEntry.getName()), name, descriptor, access); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java b/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java new file mode 100644 index 0000000..210ada0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/MethodDescriptor.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * 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.mapping; + +import com.google.common.collect.Lists; +import cuchaz.enigma.utils.Utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class MethodDescriptor { + + private List argumentDescs; + private TypeDescriptor returnDesc; + + public MethodDescriptor(String desc) { + try { + this.argumentDescs = Lists.newArrayList(); + int i = 0; + while (i < desc.length()) { + char c = desc.charAt(i); + if (c == '(') { + assert (this.argumentDescs.isEmpty()); + assert (this.returnDesc == null); + i++; + } else if (c == ')') { + i++; + break; + } else { + String type = TypeDescriptor.parseFirst(desc.substring(i)); + this.argumentDescs.add(new TypeDescriptor(type)); + i += type.length(); + } + } + this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i))); + } catch (Exception ex) { + throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex); + } + } + + public MethodDescriptor(List argumentDescs, TypeDescriptor returnDesc) { + this.argumentDescs = argumentDescs; + this.returnDesc = returnDesc; + } + + public List getArgumentDescs() { + return this.argumentDescs; + } + + public TypeDescriptor getReturnDesc() { + return this.returnDesc; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append("("); + for (TypeDescriptor desc : this.argumentDescs) { + buf.append(desc); + } + buf.append(")"); + buf.append(this.returnDesc); + return buf.toString(); + } + + public Iterable types() { + List descs = Lists.newArrayList(); + descs.addAll(this.argumentDescs); + descs.add(this.returnDesc); + return descs; + } + + @Override + public boolean equals(Object other) { + return other instanceof MethodDescriptor && equals((MethodDescriptor) other); + } + + public boolean equals(MethodDescriptor other) { + return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc); + } + + @Override + public int hashCode() { + return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode()); + } + + public boolean hasClass(ClassEntry classEntry) { + for (TypeDescriptor desc : types()) { + if (desc.containsType() && desc.getOwnerEntry().equals(classEntry)) { + return true; + } + } + return false; + } + + public MethodDescriptor remap(Function remapper) { + List argumentDescs = new ArrayList<>(this.argumentDescs.size()); + for (TypeDescriptor desc : this.argumentDescs) { + argumentDescs.add(desc.remap(remapper)); + } + return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper)); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java index 9c3058c..f8a5ff1 100644 --- a/src/main/java/cuchaz/enigma/mapping/MethodEntry.java +++ b/src/main/java/cuchaz/enigma/mapping/MethodEntry.java @@ -11,41 +11,27 @@ package cuchaz.enigma.mapping; +import com.google.common.base.Preconditions; import cuchaz.enigma.utils.Utils; -public class MethodEntry implements BehaviorEntry { - - private ClassEntry classEntry; - private String name; - private Signature signature; - - public MethodEntry(ClassEntry classEntry, String name, Signature signature) { - if (classEntry == null) { - throw new IllegalArgumentException("Class cannot be null!"); - } - if (name == null) { - throw new IllegalArgumentException("Method name cannot be null!"); - } - if (signature == null) { - throw new IllegalArgumentException("Method signature cannot be null!"); - } - if (name.startsWith("<")) { - throw new IllegalArgumentException("Don't use MethodEntry for a constructor!"); - } +public class MethodEntry implements Entry { + + protected final ClassEntry classEntry; + protected final String name; + protected final MethodDescriptor descriptor; + + public MethodEntry(ClassEntry classEntry, String name, MethodDescriptor descriptor) { + Preconditions.checkNotNull(classEntry, "Class cannot be null"); + Preconditions.checkNotNull(name, "Method name cannot be null"); + Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null"); this.classEntry = classEntry; this.name = name; - this.signature = signature; - } - - public MethodEntry(MethodEntry other, String newClassName) { - this.classEntry = new ClassEntry(newClassName); - this.name = other.name; - this.signature = other.signature; + this.descriptor = descriptor; } @Override - public ClassEntry getClassEntry() { + public ClassEntry getOwnerClassEntry() { return this.classEntry; } @@ -54,9 +40,12 @@ public class MethodEntry implements BehaviorEntry { return this.name; } - @Override - public Signature getSignature() { - return this.signature; + public MethodDescriptor getDesc() { + return this.descriptor; + } + + public boolean isConstructor() { + return name.equals("") || name.equals(""); } @Override @@ -65,13 +54,13 @@ public class MethodEntry implements BehaviorEntry { } @Override - public MethodEntry cloneToNewClass(ClassEntry classEntry) { - return new MethodEntry(this, classEntry.getName()); + public MethodEntry updateOwnership(ClassEntry classEntry) { + return new MethodEntry(new ClassEntry(classEntry.getName()), name, descriptor); } @Override public int hashCode() { - return Utils.combineHashesOrdered(this.classEntry, this.name, this.signature); + return Utils.combineHashesOrdered(this.classEntry, this.name, this.descriptor); } @Override @@ -80,11 +69,11 @@ public class MethodEntry implements BehaviorEntry { } public boolean equals(MethodEntry other) { - return this.classEntry.equals(other.classEntry) && this.name.equals(other.name) && this.signature.equals(other.signature); + return this.classEntry.equals(other.getOwnerClassEntry()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc()); } @Override public String toString() { - return this.classEntry.getName() + "." + this.name + this.signature; + return this.classEntry.getName() + "." + this.name + this.descriptor; } } diff --git a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java index 1524ce6..2f7fe53 100644 --- a/src/main/java/cuchaz/enigma/mapping/MethodMapping.java +++ b/src/main/java/cuchaz/enigma/mapping/MethodMapping.java @@ -11,50 +11,47 @@ package cuchaz.enigma.mapping; +import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.throwables.MappingConflict; import java.util.Map; -public class MethodMapping implements Comparable, MemberMapping { +public class MethodMapping implements Comparable, MemberMapping { private String obfName; private String deobfName; - private Signature obfSignature; - private Map arguments; + private MethodDescriptor obfDescriptor; + private Map localVariables; private Mappings.EntryModifier modifier; - public MethodMapping(String obfName, Signature obfSignature) { - this(obfName, obfSignature, null, Mappings.EntryModifier.UNCHANGED); + public MethodMapping(String obfName, MethodDescriptor obfDescriptor) { + this(obfName, obfDescriptor, null, Mappings.EntryModifier.UNCHANGED); } - public MethodMapping(String obfName, Signature obfSignature, String deobfName) { - this(obfName, obfSignature, deobfName, Mappings.EntryModifier.UNCHANGED); + public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName) { + this(obfName, obfDescriptor, deobfName, Mappings.EntryModifier.UNCHANGED); } - public MethodMapping(String obfName, Signature obfSignature, String deobfName, Mappings.EntryModifier modifier) { - if (obfName == null) { - throw new IllegalArgumentException("obf name cannot be null!"); - } - if (obfSignature == null) { - throw new IllegalArgumentException("obf signature cannot be null!"); - } + public MethodMapping(String obfName, MethodDescriptor obfDescriptor, String deobfName, Mappings.EntryModifier modifier) { + Preconditions.checkNotNull(obfName, "Method obf name cannot be null"); + Preconditions.checkNotNull(obfDescriptor, "Method obf desc cannot be null"); this.obfName = obfName; this.deobfName = NameValidator.validateMethodName(deobfName); - this.obfSignature = obfSignature; - this.arguments = Maps.newTreeMap(); + this.obfDescriptor = obfDescriptor; + this.localVariables = Maps.newTreeMap(); this.modifier = modifier; } - public MethodMapping(MethodMapping other, ClassNameReplacer obfClassNameReplacer) { + public MethodMapping(MethodMapping other, Translator translator) { this.obfName = other.obfName; this.deobfName = other.deobfName; this.modifier = other.modifier; - this.obfSignature = new Signature(other.obfSignature, obfClassNameReplacer); - this.arguments = Maps.newTreeMap(); - for (Map.Entry entry : other.arguments.entrySet()) { - this.arguments.put(entry.getKey(), new ArgumentMapping(entry.getValue())); + this.obfDescriptor = translator.getTranslatedMethodDesc(other.obfDescriptor); + this.localVariables = Maps.newTreeMap(); + for (Map.Entry entry : other.localVariables.entrySet()) { + this.localVariables.put(entry.getKey(), new LocalVariableMapping(entry.getValue())); } } @@ -84,56 +81,56 @@ public class MethodMapping implements Comparable, MemberMapping arguments() { - return this.arguments.values(); + public Iterable arguments() { + return this.localVariables.values(); } - public void addArgumentMapping(ArgumentMapping argumentMapping) throws MappingConflict { - if (this.arguments.containsKey(argumentMapping.getIndex())) { - throw new MappingConflict("argument", argumentMapping.getName(), this.arguments.get(argumentMapping.getIndex()).getName()); + public void addArgumentMapping(LocalVariableMapping localVariableMapping) throws MappingConflict { + if (this.localVariables.containsKey(localVariableMapping.getIndex())) { + throw new MappingConflict("argument", localVariableMapping.getName(), this.localVariables.get(localVariableMapping.getIndex()).getName()); } - this.arguments.put(argumentMapping.getIndex(), argumentMapping); + this.localVariables.put(localVariableMapping.getIndex(), localVariableMapping); } - public String getObfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); + public String getObfLocalVariableName(int index) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping != null) { + return localVariableMapping.getName(); } return null; } - public String getDeobfArgumentName(int index) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping != null) { - return argumentMapping.getName(); + public String getDeobfLocalVariableName(int index) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping != null) { + return localVariableMapping.getName(); } return null; } - public void setArgumentName(int index, String name) { - ArgumentMapping argumentMapping = this.arguments.get(index); - if (argumentMapping == null) { - argumentMapping = new ArgumentMapping(index, name); - boolean wasAdded = this.arguments.put(index, argumentMapping) == null; + public void setLocalVariableName(int index, String name) { + LocalVariableMapping localVariableMapping = this.localVariables.get(index); + if (localVariableMapping == null) { + localVariableMapping = new LocalVariableMapping(index, name); + boolean wasAdded = this.localVariables.put(index, localVariableMapping) == null; assert (wasAdded); } else { - argumentMapping.setName(name); + localVariableMapping.setName(name); } } - public void removeArgumentName(int index) { - boolean wasRemoved = this.arguments.remove(index) != null; + public void removeLocalVariableName(int index) { + boolean wasRemoved = this.localVariables.remove(index) != null; assert (wasRemoved); } @@ -146,14 +143,14 @@ public class MethodMapping implements Comparable, MemberMapping "); - buf.append(argumentMapping.getName()); + buf.append(localVariableMapping.getName()); buf.append("\n"); } return buf.toString(); @@ -161,12 +158,12 @@ public class MethodMapping implements Comparable, MemberMapping, MemberMapping - { + MethodDescriptor newDescriptor = obfDescriptor.remap(className -> { if (className.equals(oldObfClassName)) { return newObfClassName; } - return null; + return className; }); - if (!newSignature.equals(this.obfSignature)) { - this.obfSignature = newSignature; + if (!newDescriptor.equals(this.obfDescriptor)) { + this.obfDescriptor = newDescriptor; return true; } return false; } - public boolean isConstructor() { - return this.obfName.startsWith("<"); - } - @Override - public BehaviorEntry getObfEntry(ClassEntry classEntry) { - if (isConstructor()) { - return new ConstructorEntry(classEntry, this.obfSignature); - } else { - return new MethodEntry(classEntry, this.obfName, this.obfSignature); - } + public MethodEntry getObfEntry(ClassEntry classEntry) { + return new MethodEntry(classEntry, this.obfName, this.obfDescriptor); } public Mappings.EntryModifier getModifier() { diff --git a/src/main/java/cuchaz/enigma/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/mapping/NameValidator.java index aa3dc4d..f178093 100644 --- a/src/main/java/cuchaz/enigma/mapping/NameValidator.java +++ b/src/main/java/cuchaz/enigma/mapping/NameValidator.java @@ -12,7 +12,6 @@ package cuchaz.enigma.mapping; import cuchaz.enigma.throwables.IllegalNameException; -import javassist.bytecode.Descriptor; import java.util.Arrays; import java.util.List; @@ -23,11 +22,11 @@ public class NameValidator { private static final Pattern IdentifierPattern; private static final Pattern ClassPattern; private static final List ReservedWords = Arrays.asList( - "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", - "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", - "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", - "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", - "long", "strictfp", "volatile", "const", "float", "native", "super", "while" + "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized", + "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte", + "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch", + "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally", + "long", "strictfp", "volatile", "const", "float", "native", "super", "while" ); static { @@ -43,10 +42,10 @@ public class NameValidator { if (!ClassPattern.matcher(name).matches() || ReservedWords.contains(name)) { throw new IllegalNameException(name, "This doesn't look like a legal class name"); } - if (packageRequired && new ClassEntry(name).getPackageName() == null) { + if (packageRequired && ClassEntry.getPackageName(name) == null) { throw new IllegalNameException(name, "Class must be in a package"); } - return Descriptor.toJvmName(name); + return name; } public static String validateFieldName(String name) { diff --git a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java index 33d930d..9300656 100644 --- a/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java +++ b/src/main/java/cuchaz/enigma/mapping/ProcyonEntryFactory.java @@ -12,12 +12,18 @@ package cuchaz.enigma.mapping; import com.strobel.assembler.metadata.*; +import cuchaz.enigma.bytecode.AccessFlags; import java.util.List; public class ProcyonEntryFactory { + private final ReferencedEntryPool entryPool; - private static String getErasedSignature(MemberReference def) { + public ProcyonEntryFactory(ReferencedEntryPool entryPool) { + this.entryPool = entryPool; + } + + private String getErasedSignature(MemberReference def) { if (!(def instanceof MethodReference)) return def.getErasedSignature(); MethodReference methodReference = (MethodReference) def; @@ -41,27 +47,23 @@ public class ProcyonEntryFactory { return builder.toString(); } - public static FieldEntry getFieldEntry(MemberReference def) { - return new FieldEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Type(def.getErasedSignature())); + public FieldEntry getFieldEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getField(classEntry, def.getName(), def.getErasedSignature()); } - public static MethodEntry getMethodEntry(MemberReference def) { - return new MethodEntry(new ClassEntry(def.getDeclaringType().getInternalName()), def.getName(), new Signature(getErasedSignature(def))); + public FieldDefEntry getFieldDefEntry(FieldDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), new AccessFlags(def.getModifiers())); } - public static ConstructorEntry getConstructorEntry(MethodReference def) { - if (def.isTypeInitializer()) { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName())); - } else { - return new ConstructorEntry(new ClassEntry(def.getDeclaringType().getInternalName()), new Signature(def.getErasedSignature())); - } + public MethodEntry getMethodEntry(MemberReference def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return entryPool.getMethod(classEntry, def.getName(), getErasedSignature(def)); } - public static BehaviorEntry getBehaviorEntry(MethodReference def) { - if (def.isConstructor() || def.isTypeInitializer()) { - return getConstructorEntry(def); - } else { - return getMethodEntry(def); - } + public MethodDefEntry getMethodDefEntry(MethodDefinition def) { + ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName()); + return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), new AccessFlags(def.getModifiers())); } } diff --git a/src/main/java/cuchaz/enigma/mapping/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/mapping/ReferencedEntryPool.java new file mode 100644 index 0000000..2abc76c --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/ReferencedEntryPool.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * 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.mapping; + +import java.util.HashMap; +import java.util.Map; + +public class ReferencedEntryPool { + private final Map classEntries = new HashMap<>(); + private final Map> methodEntries = new HashMap<>(); + private final Map> fieldEntries = new HashMap<>(); + + public ClassEntry getClass(String name) { + return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(name)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) { + return getMethod(ownerEntry, name, new MethodDescriptor(desc)); + } + + public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) { + String key = name + desc.toString(); + return getClassMethods(ownerEntry.getName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) { + return getField(ownerEntry, name, new TypeDescriptor(desc)); + } + + public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) { + return getClassFields(ownerEntry.getName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc)); + } + + private Map getClassMethods(String name) { + return methodEntries.computeIfAbsent(name, s -> new HashMap<>()); + } + + private Map getClassFields(String name) { + return fieldEntries.computeIfAbsent(name, s -> new HashMap<>()); + } +} diff --git a/src/main/java/cuchaz/enigma/mapping/Signature.java b/src/main/java/cuchaz/enigma/mapping/Signature.java deleted file mode 100644 index 78130d6..0000000 --- a/src/main/java/cuchaz/enigma/mapping/Signature.java +++ /dev/null @@ -1,106 +0,0 @@ -/******************************************************************************* - * 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.mapping; - -import com.google.common.collect.Lists; -import cuchaz.enigma.utils.Utils; - -import java.util.List; - -public class Signature { - - private List argumentTypes; - private Type returnType; - - public Signature(String signature) { - try { - this.argumentTypes = Lists.newArrayList(); - int i = 0; - while (i < signature.length()) { - char c = signature.charAt(i); - if (c == '(') { - assert (this.argumentTypes.isEmpty()); - assert (this.returnType == null); - i++; - } else if (c == ')') { - i++; - break; - } else { - String type = Type.parseFirst(signature.substring(i)); - this.argumentTypes.add(new Type(type)); - i += type.length(); - } - } - this.returnType = new Type(Type.parseFirst(signature.substring(i))); - } catch (Exception ex) { - throw new IllegalArgumentException("Unable to parse signature: " + signature, ex); - } - } - - public Signature(Signature other, ClassNameReplacer replacer) { - this.argumentTypes = Lists.newArrayList(other.argumentTypes); - for (int i = 0; i < this.argumentTypes.size(); i++) { - this.argumentTypes.set(i, new Type(this.argumentTypes.get(i), replacer)); - } - this.returnType = new Type(other.returnType, replacer); - } - - public List getArgumentTypes() { - return this.argumentTypes; - } - - public Type getReturnType() { - return this.returnType; - } - - @Override - public String toString() { - StringBuilder buf = new StringBuilder(); - buf.append("("); - for (Type type : this.argumentTypes) { - buf.append(type); - } - buf.append(")"); - buf.append(this.returnType); - return buf.toString(); - } - - public Iterable types() { - List types = Lists.newArrayList(); - types.addAll(this.argumentTypes); - types.add(this.returnType); - return types; - } - - @Override - public boolean equals(Object other) { - return other instanceof Signature && equals((Signature) other); - } - - public boolean equals(Signature other) { - return this.argumentTypes.equals(other.argumentTypes) && this.returnType.equals(other.returnType); - } - - @Override - public int hashCode() { - return Utils.combineHashesOrdered(this.argumentTypes.hashCode(), this.returnType.hashCode()); - } - - public boolean hasClass(ClassEntry classEntry) { - for (Type type : types()) { - if (type.hasClass() && type.getClassEntry().equals(classEntry)) { - return true; - } - } - return false; - } -} diff --git a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java index 17e3187..4bbde54 100644 --- a/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java +++ b/src/main/java/cuchaz/enigma/mapping/TranslationDirection.java @@ -13,15 +13,21 @@ package cuchaz.enigma.mapping; public enum TranslationDirection { - Deobfuscating { + DEOBFUSCATING { @Override public T choose(T deobfChoice, T obfChoice) { + if (deobfChoice == null) { + return obfChoice; + } return deobfChoice; } }, - Obfuscating { + OBFUSCATING { @Override public T choose(T deobfChoice, T obfChoice) { + if (obfChoice == null) { + return deobfChoice; + } return obfChoice; } }; diff --git a/src/main/java/cuchaz/enigma/mapping/Translator.java b/src/main/java/cuchaz/enigma/mapping/Translator.java index 8d464fc..59bdf1a 100644 --- a/src/main/java/cuchaz/enigma/mapping/Translator.java +++ b/src/main/java/cuchaz/enigma/mapping/Translator.java @@ -11,332 +11,50 @@ package cuchaz.enigma.mapping; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import cuchaz.enigma.analysis.TranslationIndex; +public interface Translator { + ClassEntry getTranslatedClass(ClassEntry entry); -import java.util.List; -import java.util.Map; + ClassDefEntry getTranslatedClassDef(ClassDefEntry entry); -public class Translator { + FieldEntry getTranslatedField(FieldEntry entry); - private TranslationDirection direction; - private Map classes; - private TranslationIndex index; + FieldDefEntry getTranslatedFieldDef(FieldDefEntry entry); - private ClassNameReplacer classNameReplacer = className -> translateEntry(new ClassEntry(className)).getName(); + MethodEntry getTranslatedMethod(MethodEntry entry); - public Translator() { - this.direction = null; - this.classes = Maps.newHashMap(); - this.index = new TranslationIndex(); - } + MethodDefEntry getTranslatedMethodDef(MethodDefEntry entry); - public Translator(TranslationDirection direction, Map classes, TranslationIndex index) { - this.direction = direction; - this.classes = classes; - this.index = index; - } + LocalVariableEntry getTranslatedVariable(LocalVariableEntry entry); - public TranslationDirection getDirection() { - return direction; - } + LocalVariableDefEntry getTranslatedVariableDef(LocalVariableDefEntry entry); - public TranslationIndex getTranslationIndex() { - return index; - } + TypeDescriptor getTranslatedTypeDesc(TypeDescriptor desc); - @SuppressWarnings("unchecked") - public T translateEntry(T entry) { - if (entry instanceof ClassEntry) { - return (T) translateEntry((ClassEntry) entry); - } else if (entry instanceof FieldEntry) { - return (T) translateEntry((FieldEntry) entry); - } else if (entry instanceof MethodEntry) { - return (T) translateEntry((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return (T) translateEntry((ConstructorEntry) entry); - } else if (entry instanceof ArgumentEntry) { - return (T) translateEntry((ArgumentEntry) entry); - } else if (entry instanceof LocalVariableEntry) { - return (T) translateEntry((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } + MethodDescriptor getTranslatedMethodDesc(MethodDescriptor descriptor); - public String translate(T entry) { - if (entry instanceof ClassEntry) { - return translate((ClassEntry) entry); + @SuppressWarnings("unchecked") + default T getTranslatedEntry(T entry) { + if (entry instanceof ClassDefEntry) { + return (T) getTranslatedClassDef((ClassDefEntry) entry); + } else if (entry instanceof ClassEntry) { + return (T) getTranslatedClass((ClassEntry) entry); + } else if (entry instanceof FieldDefEntry) { + return (T) getTranslatedFieldDef((FieldDefEntry) entry); + } else if (entry instanceof MethodDefEntry) { + return (T) getTranslatedMethodDef((MethodDefEntry) entry); } else if (entry instanceof FieldEntry) { - return translate((FieldEntry) entry); + return (T) getTranslatedField((FieldEntry) entry); } else if (entry instanceof MethodEntry) { - return translate((MethodEntry) entry); - } else if (entry instanceof ConstructorEntry) { - return translate(entry); - } else if (entry instanceof ArgumentEntry) { - return translate((ArgumentEntry) entry); + return (T) getTranslatedMethod((MethodEntry) entry); + } else if (entry instanceof LocalVariableDefEntry) { + return (T) getTranslatedVariableDef((LocalVariableDefEntry) entry); } else if (entry instanceof LocalVariableEntry) { - return translate((LocalVariableEntry) entry); - } else { - throw new Error("Unknown entry type: " + entry.getClass().getName()); - } - } - - public String translate(LocalVariableEntry in) { - LocalVariableEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } - - public LocalVariableEntry translateEntry(LocalVariableEntry in) { - // TODO: Implement it - return in; - } - - public String translate(ClassEntry in) { - ClassEntry translated = translateEntry(in); - if (translated.equals(in)) { - return null; - } - return translated.getName(); - } - - public String translateClass(String className) { - return translate(new ClassEntry(className)); - } - - public ClassEntry translateEntry(ClassEntry in) { - - if (in.isInnerClass()) { - - // translate as much of the class chain as we can - List mappingsChain = getClassMappingChain(in); - String[] obfClassNames = in.getName().split("\\$"); - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < obfClassNames.length; i++) { - boolean isFirstClass = buf.length() == 0; - String className = null; - ClassMapping classMapping = mappingsChain.get(i); - if (classMapping != null) { - className = this.direction.choose( - classMapping.getDeobfName(), - isFirstClass ? classMapping.getObfFullName() : classMapping.getObfSimpleName() - ); - } - if (className == null) { - className = obfClassNames[i]; - } - if (!isFirstClass) { - buf.append("$"); - } - buf.append(className); - } - return new ClassEntry(buf.toString()); - - } else { - - // normal classes are easy - ClassMapping classMapping = this.classes.get(in.getName()); - if (classMapping == null) { - return in; - } - return this.direction.choose( - classMapping.getDeobfName() != null ? new ClassEntry(classMapping.getDeobfName()) : in, - new ClassEntry(classMapping.getObfFullName()) - ); - } - } - - public String translate(FieldEntry in) { - - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in); - if (resolvedClassEntry != null) { - - // look for the class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the field - String translatedName = this.direction.choose( - classMapping.getDeobfFieldName(in.getName(), in.getType()), - classMapping.getObfFieldName(in.getName(), translateType(in.getType())) - ); - if (translatedName != null) { - return translatedName; - } - } - } - return null; - } - - public FieldEntry translateEntry(FieldEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new FieldEntry(translateEntry(in.getClassEntry()), name, translateType(in.getType())); - } - - public String translate(MethodEntry in) { - // resolve the class entry - ClassEntry resolvedClassEntry = this.index.resolveEntryClass(in, true); - if (resolvedClassEntry != null) { - - // look for class - ClassMapping classMapping = findClassMapping(resolvedClassEntry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getName(), in.getSignature()), - classMapping.getMethodByDeobf(in.getName(), translateSignature(in.getSignature())) - ); - if (methodMapping != null) { - return this.direction.choose(methodMapping.getDeobfName(), methodMapping.getObfName()); - } - } - } - return null; - } - - public MethodEntry translateEntry(MethodEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new MethodEntry(translateEntry(in.getClassEntry()), name, translateSignature(in.getSignature())); - } - - public ConstructorEntry translateEntry(ConstructorEntry in) { - if (in.isStatic()) { - return new ConstructorEntry(translateEntry(in.getClassEntry())); - } else { - return new ConstructorEntry(translateEntry(in.getClassEntry()), translateSignature(in.getSignature())); - } - } - - public BehaviorEntry translateEntry(BehaviorEntry in) { - if (in instanceof MethodEntry) { - return translateEntry((MethodEntry) in); - } else if (in instanceof ConstructorEntry) { - return translateEntry((ConstructorEntry) in); - } - throw new Error("Wrong entry type!"); - } - - // TODO: support not identical behavior (specific to constructor) - public String translate(ArgumentEntry in) { - String classTranslate = translateArgument(in); - - // Not found in this class - if (classTranslate == null) { - List ancestry = this.index.getAncestry(in.getClassEntry()); - - // Check in mother class for the arg - for (ClassEntry entry : ancestry) { - ArgumentEntry motherArg = in.cloneToNewClass(entry); - if (this.index.entryExists(motherArg)) { - String result = translateArgument(motherArg); - if (result != null) - return result; - } - } - } - return classTranslate; - } - - public String translateArgument(ArgumentEntry in) { - // look for identical behavior in superclasses - ClassEntry entry = in.getClassEntry(); - - if (entry != null) { - // look for the class - ClassMapping classMapping = findClassMapping(entry); - if (classMapping != null) { - - // look for the method - MethodMapping methodMapping = this.direction.choose( - classMapping.getMethodByObf(in.getMethodName(), in.getMethodSignature()), - classMapping.getMethodByDeobf(in.getMethodName(), translateSignature(in.getMethodSignature())) - ); - if (methodMapping != null) { - return this.direction.choose( - methodMapping.getDeobfArgumentName(in.getIndex()), - methodMapping.getObfArgumentName(in.getIndex()) - ); - } - } - } - return null; - } - - public ArgumentEntry translateEntry(ArgumentEntry in) { - String name = translate(in); - if (name == null) { - name = in.getName(); - } - return new ArgumentEntry(translateEntry(in.getBehaviorEntry()), in.getIndex(), name); - } - - public Type translateType(Type type) { - return new Type(type, this.classNameReplacer); - } - - public Signature translateSignature(Signature signature) { - return new Signature(signature, this.classNameReplacer); - } - - private ClassMapping findClassMapping(ClassEntry in) { - List mappingChain = getClassMappingChain(in); - return mappingChain.get(mappingChain.size() - 1); - } - - private List getClassMappingChain(ClassEntry in) { - - // get a list of all the classes in the hierarchy - String[] parts = in.getName().split("\\$"); - List mappingsChain = Lists.newArrayList(); - - // get mappings for the outer class - ClassMapping outerClassMapping = this.classes.get(parts[0]); - mappingsChain.add(outerClassMapping); - - for (int i = 1; i < parts.length; i++) { - - // get mappings for the inner class - ClassMapping innerClassMapping = null; - if (outerClassMapping != null) { - innerClassMapping = this.direction.choose( - outerClassMapping.getInnerClassByObfSimple(parts[i]), - outerClassMapping.getInnerClassByDeobfThenObfSimple(parts[i]) - ); - } - mappingsChain.add(innerClassMapping); - outerClassMapping = innerClassMapping; - } - - assert (mappingsChain.size() == parts.length); - return mappingsChain; - } - - public Mappings.EntryModifier getModifier(Entry entry) { - ClassMapping classMapping = findClassMapping(entry.getClassEntry()); - if (classMapping != null && !entry.getName().equals("")) { - if (entry instanceof ClassEntry) - return classMapping.getModifier(); - else if (entry instanceof FieldEntry) { - FieldMapping fieldMapping = classMapping.getFieldByObf(entry.getName(), ((FieldEntry) entry).getType()); - return fieldMapping != null ? fieldMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } else if (entry instanceof BehaviorEntry) { - MethodMapping methodMapping = classMapping.getMethodByObf(entry.getName(), ((BehaviorEntry) entry).getSignature()); - return methodMapping != null ? methodMapping.getModifier() : Mappings.EntryModifier.UNCHANGED; - } else - throw new Error("Unknown entry type: " + entry.getClass().getName()); + return (T) getTranslatedVariable((LocalVariableEntry) entry); + } else if (entry instanceof TypeDescriptor) { + return (T) getTranslatedTypeDesc((TypeDescriptor) entry); + } else if (entry instanceof MethodDescriptor) { + return (T) getTranslatedMethodDesc((MethodDescriptor) entry); } - return Mappings.EntryModifier.UNCHANGED; + throw new IllegalArgumentException("Cannot translate unknown entry type"); } } diff --git a/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java new file mode 100644 index 0000000..9c0fe6d --- /dev/null +++ b/src/main/java/cuchaz/enigma/mapping/TypeDescriptor.java @@ -0,0 +1,240 @@ +/******************************************************************************* + * 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.mapping; + +import com.google.common.collect.Maps; + +import java.util.Map; +import java.util.function.Function; + +public class TypeDescriptor { + + protected final String desc; + + public TypeDescriptor(String desc) { + // don't deal with generics + // this is just for raw jvm types + if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) { + throw new IllegalArgumentException("don't use with generic types or templates: " + desc); + } + + this.desc = desc; + } + + public static String parseFirst(String in) { + + if (in == null || in.length() <= 0) { + throw new IllegalArgumentException("No desc to parse, input is empty!"); + } + + // read one desc from the input + + char c = in.charAt(0); + + // first check for void + if (c == 'V') { + return "V"; + } + + // then check for primitives + Primitive primitive = Primitive.get(c); + if (primitive != null) { + return in.substring(0, 1); + } + + // then check for classes + if (c == 'L') { + return readClass(in); + } + + // then check for templates + if (c == 'T') { + return readClass(in); + } + + // then check for arrays + int dim = countArrayDimension(in); + if (dim > 0) { + String arrayType = TypeDescriptor.parseFirst(in.substring(dim)); + return in.substring(0, dim + arrayType.length()); + } + + throw new IllegalArgumentException("don't know how to parse: " + in); + } + + private static int countArrayDimension(String in) { + int i = 0; + while (i < in.length() && in.charAt(i) == '[') + i++; + return i; + } + + private static String readClass(String in) { + // read all the characters in the buffer until we hit a ';' + // include the parameters too + StringBuilder buf = new StringBuilder(); + int depth = 0; + for (int i = 0; i < in.length(); i++) { + char c = in.charAt(i); + buf.append(c); + + if (c == '<') { + depth++; + } else if (c == '>') { + depth--; + } else if (depth == 0 && c == ';') { + return buf.toString(); + } + } + return null; + } + + public static TypeDescriptor of(String name) { + return new TypeDescriptor("L" + name + ";"); + } + + @Override + public String toString() { + return this.desc; + } + + public boolean isVoid() { + return this.desc.length() == 1 && this.desc.charAt(0) == 'V'; + } + + public boolean isPrimitive() { + return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null; + } + + public Primitive getPrimitive() { + if (!isPrimitive()) { + throw new IllegalStateException("not a primitive"); + } + return Primitive.get(this.desc.charAt(0)); + } + + public boolean isType() { + return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';'; + } + + public ClassEntry getOwnerEntry() { + if (isType()) { + String name = this.desc.substring(1, this.desc.length() - 1); + + int pos = name.indexOf('<'); + if (pos >= 0) { + // remove the parameters from the class name + name = name.substring(0, pos); + } + + return new ClassEntry(name); + + } else if (isArray() && getArrayType().isType()) { + return getArrayType().getOwnerEntry(); + } else { + throw new IllegalStateException("desc doesn't have a class"); + } + } + + public boolean isArray() { + return this.desc.charAt(0) == '['; + } + + public int getArrayDimension() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return countArrayDimension(this.desc); + } + + public TypeDescriptor getArrayType() { + if (!isArray()) { + throw new IllegalStateException("not an array"); + } + return new TypeDescriptor(this.desc.substring(getArrayDimension(), this.desc.length())); + } + + public boolean containsType() { + return isType() || (isArray() && getArrayType().containsType()); + } + + @Override + public boolean equals(Object other) { + return other instanceof TypeDescriptor && equals((TypeDescriptor) other); + } + + public boolean equals(TypeDescriptor other) { + return this.desc.equals(other.desc); + } + + @Override + public int hashCode() { + return this.desc.hashCode(); + } + + public TypeDescriptor remap(Function remapper) { + String desc = this.desc; + if (isType() || (isArray() && containsType())) { + String replacedName = remapper.apply(this.getOwnerEntry().getName()); + if (replacedName != null) { + if (this.isType()) { + desc = "L" + replacedName + ";"; + } else { + desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";"; + } + } + } + return new TypeDescriptor(desc); + } + + private static String getArrayPrefix(int dimension) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < dimension; i++) { + buf.append("["); + } + return buf.toString(); + } + + public enum Primitive { + Byte('B'), + Character('C'), + Short('S'), + Integer('I'), + Long('J'), + Float('F'), + Double('D'), + Boolean('Z'); + + private static final Map lookup; + + static { + lookup = Maps.newTreeMap(); + for (Primitive val : values()) { + lookup.put(val.getCode(), val); + } + } + + private char code; + + Primitive(char code) { + this.code = code; + } + + public static Primitive get(char code) { + return lookup.get(code); + } + + public char getCode() { + return this.code; + } + } +} diff --git a/src/test/java/cuchaz/enigma/TestDeobfed.java b/src/test/java/cuchaz/enigma/TestDeobfed.java index e6c1b74..fbdebd6 100644 --- a/src/test/java/cuchaz/enigma/TestDeobfed.java +++ b/src/test/java/cuchaz/enigma/TestDeobfed.java @@ -12,6 +12,8 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.analysis.ParsedJar; +import cuchaz.enigma.mapping.ReferencedEntryPool; import org.junit.BeforeClass; import org.junit.Test; @@ -30,8 +32,8 @@ public class TestDeobfed { public static void beforeClass() throws Exception { jar = new JarFile("build/test-deobf/translation.jar"); - index = new JarIndex(); - index.indexJar(jar, true); + index = new JarIndex(new ReferencedEntryPool()); + index.indexJar(new ParsedJar(jar), true); } @Test diff --git a/src/test/java/cuchaz/enigma/TestEntryFactory.java b/src/test/java/cuchaz/enigma/TestEntryFactory.java index 1c527f5..067dcbf 100644 --- a/src/test/java/cuchaz/enigma/TestEntryFactory.java +++ b/src/test/java/cuchaz/enigma/TestEntryFactory.java @@ -25,7 +25,7 @@ public class TestEntryFactory { } public static FieldEntry newField(ClassEntry classEntry, String fieldName, String fieldType) { - return new FieldEntry(classEntry, fieldName, new Type(fieldType)); + return new FieldEntry(classEntry, fieldName, new TypeDescriptor(fieldType)); } public static MethodEntry newMethod(String className, String methodName, String methodSignature) { @@ -33,30 +33,14 @@ public class TestEntryFactory { } public static MethodEntry newMethod(ClassEntry classEntry, String methodName, String methodSignature) { - return new MethodEntry(classEntry, methodName, new Signature(methodSignature)); + return new MethodEntry(classEntry, methodName, new MethodDescriptor(methodSignature)); } - public static ConstructorEntry newConstructor(String className, String signature) { - return newConstructor(newClass(className), signature); + public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference<>(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); } - public static ConstructorEntry newConstructor(ClassEntry classEntry, String signature) { - return new ConstructorEntry(classEntry, new Signature(signature)); - } - - public static EntryReference newFieldReferenceByMethod(FieldEntry fieldEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference(fieldEntry, "", newMethod(callerClassName, callerName, callerSignature)); - } - - public static EntryReference newFieldReferenceByConstructor(FieldEntry fieldEntry, String callerClassName, String callerSignature) { - return new EntryReference(fieldEntry, "", newConstructor(callerClassName, callerSignature)); - } - - public static EntryReference newBehaviorReferenceByMethod(BehaviorEntry behaviorEntry, String callerClassName, String callerName, String callerSignature) { - return new EntryReference(behaviorEntry, "", newMethod(callerClassName, callerName, callerSignature)); - } - - public static EntryReference newBehaviorReferenceByConstructor(BehaviorEntry behaviorEntry, String callerClassName, String callerSignature) { - return new EntryReference(behaviorEntry, "", newConstructor(callerClassName, callerSignature)); + public static EntryReference newBehaviorReferenceByMethod(MethodEntry methodEntry, String callerClassName, String callerName, String callerSignature) { + return new EntryReference<>(methodEntry, "", newMethod(callerClassName, callerName, callerSignature)); } } diff --git a/src/test/java/cuchaz/enigma/TestInnerClasses.java b/src/test/java/cuchaz/enigma/TestInnerClasses.java index 38db0df..30e127e 100644 --- a/src/test/java/cuchaz/enigma/TestInnerClasses.java +++ b/src/test/java/cuchaz/enigma/TestInnerClasses.java @@ -12,7 +12,9 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.JarIndex; +import cuchaz.enigma.analysis.ParsedJar; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.ReferencedEntryPool; import org.junit.Test; import java.util.jar.JarFile; @@ -26,16 +28,10 @@ import static org.hamcrest.Matchers.nullValue; public class TestInnerClasses { - private static final ClassEntry AnonymousOuter = newClass("a"); - private static final ClassEntry AnonymousInner = newClass("a$1"); private static final ClassEntry SimpleOuter = newClass("d"); private static final ClassEntry SimpleInner = newClass("d$a"); private static final ClassEntry ConstructorArgsOuter = newClass("c"); private static final ClassEntry ConstructorArgsInner = newClass("c$a"); - private static final ClassEntry AnonymousWithScopeArgsOuter = newClass("b"); - private static final ClassEntry AnonymousWithScopeArgsInner = newClass("b$1"); - private static final ClassEntry AnonymousWithOuterAccessOuter = newClass("e"); - private static final ClassEntry AnonymousWithOuterAccessInner = newClass("e$1"); private static final ClassEntry ClassTreeRoot = newClass("f"); private static final ClassEntry ClassTreeLevel1 = newClass("f$a"); private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); @@ -45,9 +41,9 @@ public class TestInnerClasses { public TestInnerClasses() throws Exception { - index = new JarIndex(); + index = new JarIndex(new ReferencedEntryPool()); JarFile jar = new JarFile("build/test-obf/innerClasses.jar"); - index.indexJar(jar, true); + index.indexJar(new ParsedJar(jar), true); deobfuscator = new Deobfuscator(jar); } @@ -55,42 +51,16 @@ public class TestInnerClasses { public void simple() { assertThat(index.getOuterClass(SimpleInner), is(SimpleOuter)); assertThat(index.getInnerClasses(SimpleOuter), containsInAnyOrder(SimpleInner)); - assertThat(index.isAnonymousClass(SimpleInner), is(false)); decompile(SimpleOuter); } - @Test - public void anonymous() { - assertThat(index.getOuterClass(AnonymousInner), is(AnonymousOuter)); - assertThat(index.getInnerClasses(AnonymousOuter), containsInAnyOrder(AnonymousInner)); - assertThat(index.isAnonymousClass(AnonymousInner), is(true)); - decompile(AnonymousOuter); - } - @Test public void constructorArgs() { assertThat(index.getOuterClass(ConstructorArgsInner), is(ConstructorArgsOuter)); assertThat(index.getInnerClasses(ConstructorArgsOuter), containsInAnyOrder(ConstructorArgsInner)); - assertThat(index.isAnonymousClass(ConstructorArgsInner), is(false)); decompile(ConstructorArgsOuter); } - @Test - public void anonymousWithScopeArgs() { - assertThat(index.getOuterClass(AnonymousWithScopeArgsInner), is(AnonymousWithScopeArgsOuter)); - assertThat(index.getInnerClasses(AnonymousWithScopeArgsOuter), containsInAnyOrder(AnonymousWithScopeArgsInner)); - assertThat(index.isAnonymousClass(AnonymousWithScopeArgsInner), is(true)); - decompile(AnonymousWithScopeArgsOuter); - } - - @Test - public void anonymousWithOuterAccess() { - assertThat(index.getOuterClass(AnonymousWithOuterAccessInner), is(AnonymousWithOuterAccessOuter)); - assertThat(index.getInnerClasses(AnonymousWithOuterAccessOuter), containsInAnyOrder(AnonymousWithOuterAccessInner)); - assertThat(index.isAnonymousClass(AnonymousWithOuterAccessInner), is(true)); - decompile(AnonymousWithOuterAccessOuter); - } - @Test public void classTree() { @@ -101,8 +71,7 @@ public class TestInnerClasses { // level 1 ClassEntry fullClassEntry = new ClassEntry(ClassTreeRoot.getName() - + "$" + ClassTreeLevel1.getInnermostClassName() - ); + + "$" + ClassTreeLevel1.getInnermostClassName()); assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel1), is(ClassTreeRoot)); assertThat(index.getInnerClasses(ClassTreeLevel1), containsInAnyOrder(ClassTreeLevel2)); @@ -110,8 +79,7 @@ public class TestInnerClasses { // level 2 fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + "$" + ClassTreeLevel1.getInnermostClassName() - + "$" + ClassTreeLevel2.getInnermostClassName() - ); + + "$" + ClassTreeLevel2.getInnermostClassName()); assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel2), is(ClassTreeLevel1)); assertThat(index.getInnerClasses(ClassTreeLevel2), containsInAnyOrder(ClassTreeLevel3)); @@ -120,8 +88,7 @@ public class TestInnerClasses { fullClassEntry = new ClassEntry(ClassTreeRoot.getName() + "$" + ClassTreeLevel1.getInnermostClassName() + "$" + ClassTreeLevel2.getInnermostClassName() - + "$" + ClassTreeLevel3.getInnermostClassName() - ); + + "$" + ClassTreeLevel3.getInnermostClassName()); assertThat(index.containsObfClass(fullClassEntry), is(true)); assertThat(index.getOuterClass(ClassTreeLevel3), is(ClassTreeLevel2)); assertThat(index.getInnerClasses(ClassTreeLevel3), is(empty())); diff --git a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java index edb859a..b20b27b 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java @@ -13,22 +13,20 @@ package cuchaz.enigma; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.analysis.ParsedJar; import cuchaz.enigma.mapping.ClassEntry; +import cuchaz.enigma.mapping.MethodDefEntry; +import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.mapping.ReferencedEntryPool; import org.junit.Test; import java.io.File; import java.util.Collection; import java.util.jar.JarFile; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newClass; -import static cuchaz.enigma.TestEntryFactory.newConstructor; +import static cuchaz.enigma.TestEntryFactory.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class TestJarIndexConstructorReferences { @@ -41,90 +39,90 @@ public class TestJarIndexConstructorReferences { private ClassEntry callerClass = newClass("b"); public TestJarIndexConstructorReferences() - throws Exception { + throws Exception { File jarFile = new File("build/test-obf/constructors.jar"); - index = new JarIndex(); - index.indexJar(new JarFile(jarFile), false); + index = new JarIndex(new ReferencedEntryPool()); + index.indexJar(new ParsedJar(new JarFile(jarFile)), false); } @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder(newClass("cuchaz/enigma/inputs/Keep"), baseClass, - subClass, subsubClass, defaultClass, callerClass)); + subClass, subsubClass, defaultClass, callerClass)); } @Test @SuppressWarnings("unchecked") public void baseDefault() { - BehaviorEntry source = newConstructor(baseClass, "()V"); - Collection> references = index.getBehaviorReferences(source); + MethodEntry source = newMethod(baseClass, "", "()V"); + Collection> references = index.getMethodReferences(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "(III)V") + newBehaviorReferenceByMethod(source, callerClass.getName(), "a", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(III)V") )); } @Test @SuppressWarnings("unchecked") public void baseInt() { - BehaviorEntry source = newConstructor(baseClass, "(I)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V") + MethodEntry source = newMethod(baseClass, "", "(I)V"); + assertThat(index.getMethodReferences(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "b", "()V") )); } @Test @SuppressWarnings("unchecked") public void subDefault() { - BehaviorEntry source = newConstructor(subClass, "()V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "c", "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "(I)V") + MethodEntry source = newMethod(subClass, "", "()V"); + assertThat(index.getMethodReferences(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "c", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(I)V") )); } @Test @SuppressWarnings("unchecked") public void subInt() { - BehaviorEntry source = newConstructor(subClass, "(I)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "d", "()V"), - newBehaviorReferenceByConstructor(source, subClass.getName(), "(II)V"), - newBehaviorReferenceByConstructor(source, subsubClass.getName(), "(I)V") + MethodEntry source = newMethod(subClass, "", "(I)V"); + assertThat(index.getMethodReferences(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "d", "()V"), + newBehaviorReferenceByMethod(source, subClass.getName(), "", "(II)V"), + newBehaviorReferenceByMethod(source, subsubClass.getName(), "", "(I)V") )); } @Test @SuppressWarnings("unchecked") public void subIntInt() { - BehaviorEntry source = newConstructor(subClass, "(II)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V") + MethodEntry source = newMethod(subClass, "", "(II)V"); + assertThat(index.getMethodReferences(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "e", "()V") )); } @Test public void subIntIntInt() { - BehaviorEntry source = newConstructor(subClass, "(III)V"); - assertThat(index.getBehaviorReferences(source), is(empty())); + MethodEntry source = newMethod(subClass, "", "(III)V"); + assertThat(index.getMethodReferences(source), is(empty())); } @Test @SuppressWarnings("unchecked") public void subsubInt() { - BehaviorEntry source = newConstructor(subsubClass, "(I)V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V") + MethodEntry source = newMethod(subsubClass, "", "(I)V"); + assertThat(index.getMethodReferences(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "f", "()V") )); } @Test @SuppressWarnings("unchecked") public void defaultConstructable() { - BehaviorEntry source = newConstructor(defaultClass, "()V"); - assertThat(index.getBehaviorReferences(source), containsInAnyOrder( - newBehaviorReferenceByMethod(source, callerClass.getName(), "g", "()V") + MethodEntry source = newMethod(defaultClass, "", "()V"); + assertThat(index.getMethodReferences(source), containsInAnyOrder( + newBehaviorReferenceByMethod(source, callerClass.getName(), "g", "()V") )); } } diff --git a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java index 6246978..d1c8596 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java @@ -11,14 +11,8 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.Access; -import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.JarIndex; -import cuchaz.enigma.analysis.TranslationIndex; -import cuchaz.enigma.mapping.BehaviorEntry; -import cuchaz.enigma.mapping.ClassEntry; -import cuchaz.enigma.mapping.FieldEntry; -import cuchaz.enigma.mapping.MethodEntry; +import cuchaz.enigma.analysis.*; +import cuchaz.enigma.mapping.*; import org.junit.Test; import java.util.Collection; @@ -27,10 +21,7 @@ import java.util.jar.JarFile; import static cuchaz.enigma.TestEntryFactory.*; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.*; public class TestJarIndexInheritanceTree { @@ -45,15 +36,15 @@ public class TestJarIndexInheritanceTree { private FieldEntry numThingsField = newField(subClassB, "a", "I"); public TestJarIndexInheritanceTree() - throws Exception { - index = new JarIndex(); - index.indexJar(new JarFile("build/test-obf/inheritanceTree.jar"), false); + throws Exception { + index = new JarIndex(new ReferencedEntryPool()); + index.indexJar(new ParsedJar(new JarFile("build/test-obf/inheritanceTree.jar")), false); } @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder( - newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB + newClass("cuchaz/enigma/inputs/Keep"), baseClass, subClassA, subClassAA, subClassB )); } @@ -98,33 +89,33 @@ public class TestJarIndexInheritanceTree { // getName() entries = index.getRelatedMethodImplementations(newMethod(baseClass, "a", "()Ljava/lang/String;")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()Ljava/lang/String;"), - newMethod(subClassAA, "a", "()Ljava/lang/String;") + newMethod(baseClass, "a", "()Ljava/lang/String;"), + newMethod(subClassAA, "a", "()Ljava/lang/String;") )); entries = index.getRelatedMethodImplementations(newMethod(subClassAA, "a", "()Ljava/lang/String;")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()Ljava/lang/String;"), - newMethod(subClassAA, "a", "()Ljava/lang/String;") + newMethod(baseClass, "a", "()Ljava/lang/String;"), + newMethod(subClassAA, "a", "()Ljava/lang/String;") )); // doBaseThings() entries = index.getRelatedMethodImplementations(newMethod(baseClass, "a", "()V")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") )); entries = index.getRelatedMethodImplementations(newMethod(subClassAA, "a", "()V")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") )); entries = index.getRelatedMethodImplementations(newMethod(subClassB, "a", "()V")); assertThat(entries, containsInAnyOrder( - newMethod(baseClass, "a", "()V"), - newMethod(subClassAA, "a", "()V"), - newMethod(subClassB, "a", "()V") + newMethod(baseClass, "a", "()V"), + newMethod(subClassAA, "a", "()V"), + newMethod(subClassB, "a", "()V") )); // doBThings @@ -135,20 +126,20 @@ public class TestJarIndexInheritanceTree { @Test @SuppressWarnings("unchecked") public void fieldReferences() { - Collection> references; + Collection> references; // name references = index.getFieldReferences(nameField); assertThat(references, containsInAnyOrder( - newFieldReferenceByConstructor(nameField, baseClass.getName(), "(Ljava/lang/String;)V"), - newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;") + newFieldReferenceByMethod(nameField, baseClass.getName(), "", "(Ljava/lang/String;)V"), + newFieldReferenceByMethod(nameField, baseClass.getName(), "a", "()Ljava/lang/String;") )); // numThings references = index.getFieldReferences(numThingsField); assertThat(references, containsInAnyOrder( - newFieldReferenceByConstructor(numThingsField, subClassB.getName(), "()V"), - newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V") + newFieldReferenceByMethod(numThingsField, subClassB.getName(), "", "()V"), + newFieldReferenceByMethod(numThingsField, subClassB.getName(), "b", "()V") )); } @@ -156,37 +147,37 @@ public class TestJarIndexInheritanceTree { @SuppressWarnings("unchecked") public void behaviorReferences() { - BehaviorEntry source; - Collection> references; + MethodEntry source; + Collection> references; // baseClass constructor - source = newConstructor(baseClass, "(Ljava/lang/String;)V"); - references = index.getBehaviorReferences(source); + source = newMethod(baseClass, "", "(Ljava/lang/String;)V"); + references = index.getMethodReferences(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByConstructor(source, subClassA.getName(), "(Ljava/lang/String;)V"), - newBehaviorReferenceByConstructor(source, subClassB.getName(), "()V") + newBehaviorReferenceByMethod(source, subClassA.getName(), "", "(Ljava/lang/String;)V"), + newBehaviorReferenceByMethod(source, subClassB.getName(), "", "()V") )); // subClassA constructor - source = newConstructor(subClassA, "(Ljava/lang/String;)V"); - references = index.getBehaviorReferences(source); + source = newMethod(subClassA, "", "(Ljava/lang/String;)V"); + references = index.getMethodReferences(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByConstructor(source, subClassAA.getName(), "()V") + newBehaviorReferenceByMethod(source, subClassAA.getName(), "", "()V") )); // baseClass.getName() source = newMethod(baseClass, "a", "()Ljava/lang/String;"); - references = index.getBehaviorReferences(source); + references = index.getMethodReferences(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"), - newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V") + newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()Ljava/lang/String;"), + newBehaviorReferenceByMethod(source, subClassB.getName(), "a", "()V") )); // subclassAA.getName() source = newMethod(subClassAA, "a", "()Ljava/lang/String;"); - references = index.getBehaviorReferences(source); + references = index.getMethodReferences(source); assertThat(references, containsInAnyOrder( - newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V") + newBehaviorReferenceByMethod(source, subClassAA.getName(), "a", "()V") )); } @@ -205,22 +196,22 @@ public class TestJarIndexInheritanceTree { // methods // getName() - assertThat(index.containsObfBehavior(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false)); + assertThat(index.containsObfMethod(newMethod(baseClass, "a", "()Ljava/lang/String;")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassA, "a", "()Ljava/lang/String;")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassAA, "a", "()Ljava/lang/String;")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassB, "a", "()Ljava/lang/String;")), is(false)); // doBaseThings() - assertThat(index.containsObfBehavior(newMethod(baseClass, "a", "()V")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassA, "a", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassAA, "a", "()V")), is(true)); - assertThat(index.containsObfBehavior(newMethod(subClassB, "a", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(baseClass, "a", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassA, "a", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassAA, "a", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(subClassB, "a", "()V")), is(true)); // doBThings() - assertThat(index.containsObfBehavior(newMethod(baseClass, "b", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassA, "b", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassAA, "b", "()V")), is(false)); - assertThat(index.containsObfBehavior(newMethod(subClassB, "b", "()V")), is(true)); + assertThat(index.containsObfMethod(newMethod(baseClass, "b", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassA, "b", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassAA, "b", "()V")), is(false)); + assertThat(index.containsObfMethod(newMethod(subClassB, "b", "()V")), is(true)); } } diff --git a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java index 6cab1c8..d03f3fb 100644 --- a/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java +++ b/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java @@ -28,16 +28,16 @@ public class TestJarIndexLoneClass { private JarIndex index; public TestJarIndexLoneClass() - throws Exception { - index = new JarIndex(); - index.indexJar(new JarFile("build/test-obf/loneClass.jar"), false); + throws Exception { + index = new JarIndex(new ReferencedEntryPool()); + index.indexJar(new ParsedJar(new JarFile("build/test-obf/loneClass.jar")), false); } @Test public void obfEntries() { assertThat(index.getObfClassEntries(), containsInAnyOrder( - newClass("cuchaz/enigma/inputs/Keep"), - newClass("a") + newClass("cuchaz/enigma/inputs/Keep"), + newClass("a") )); } @@ -61,7 +61,7 @@ public class TestJarIndexLoneClass { @Test public void classInheritance() { - ClassInheritanceTreeNode node = index.getClassInheritance(new Translator(), newClass("a")); + ClassInheritanceTreeNode node = index.getClassInheritance(new DirectionalTranslator(new ReferencedEntryPool()), newClass("a")); assertThat(node, is(not(nullValue()))); assertThat(node.getObfClassName(), is("a")); assertThat(node.getChildCount(), is(0)); @@ -70,7 +70,7 @@ public class TestJarIndexLoneClass { @Test public void methodInheritance() { MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); - MethodInheritanceTreeNode node = index.getMethodInheritance(new Translator(), source); + MethodInheritanceTreeNode node = index.getMethodInheritance(new DirectionalTranslator(new ReferencedEntryPool()), source); assertThat(node, is(not(nullValue()))); assertThat(node.getMethodEntry(), is(source)); assertThat(node.getChildCount(), is(0)); @@ -78,21 +78,21 @@ public class TestJarIndexLoneClass { @Test public void classImplementations() { - ClassImplementationsTreeNode node = index.getClassImplementations(new Translator(), newClass("a")); + ClassImplementationsTreeNode node = index.getClassImplementations(new DirectionalTranslator(new ReferencedEntryPool()), newClass("a")); assertThat(node, is(nullValue())); } @Test public void methodImplementations() { MethodEntry source = newMethod("a", "a", "()Ljava/lang/String;"); - assertThat(index.getMethodImplementations(new Translator(), source), is(empty())); + assertThat(index.getMethodImplementations(new DirectionalTranslator(new ReferencedEntryPool()), source), is(empty())); } @Test public void relatedMethodImplementations() { Set entries = index.getRelatedMethodImplementations(newMethod("a", "a", "()Ljava/lang/String;")); assertThat(entries, containsInAnyOrder( - newMethod("a", "a", "()Ljava/lang/String;") + newMethod("a", "a", "()Ljava/lang/String;") )); } @@ -100,16 +100,16 @@ public class TestJarIndexLoneClass { @SuppressWarnings("unchecked") public void fieldReferences() { FieldEntry source = newField("a", "a", "Ljava/lang/String;"); - Collection> references = index.getFieldReferences(source); + Collection> references = index.getFieldReferences(source); assertThat(references, containsInAnyOrder( - newFieldReferenceByConstructor(source, "a", "(Ljava/lang/String;)V"), - newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;") + newFieldReferenceByMethod(source, "a", "", "(Ljava/lang/String;)V"), + newFieldReferenceByMethod(source, "a", "a", "()Ljava/lang/String;") )); } @Test public void behaviorReferences() { - assertThat(index.getBehaviorReferences(newMethod("a", "a", "()Ljava/lang/String;")), is(empty())); + assertThat(index.getMethodReferences(newMethod("a", "a", "()Ljava/lang/String;")), is(empty())); } @Test @@ -122,11 +122,6 @@ public class TestJarIndexLoneClass { assertThat(index.getOuterClass(newClass("a")), is(nullValue())); } - @Test - public void isAnonymousClass() { - assertThat(index.isAnonymousClass(newClass("a")), is(false)); - } - @Test public void interfaces() { assertThat(index.getInterfaces("a"), is(empty())); @@ -149,7 +144,7 @@ public class TestJarIndexLoneClass { assertThat(index.containsObfField(newField("a", "a", "Ljava/lang/String;")), is(true)); assertThat(index.containsObfField(newField("a", "b", "Ljava/lang/String;")), is(false)); assertThat(index.containsObfField(newField("a", "a", "LFoo;")), is(false)); - assertThat(index.containsObfBehavior(newMethod("a", "a", "()Ljava/lang/String;")), is(true)); - assertThat(index.containsObfBehavior(newMethod("a", "b", "()Ljava/lang/String;")), is(false)); + assertThat(index.containsObfMethod(newMethod("a", "a", "()Ljava/lang/String;")), is(true)); + assertThat(index.containsObfMethod(newMethod("a", "b", "()Ljava/lang/String;")), is(false)); } } diff --git a/src/test/java/cuchaz/enigma/TestMethodDescriptor.java b/src/test/java/cuchaz/enigma/TestMethodDescriptor.java new file mode 100644 index 0000000..48c46e5 --- /dev/null +++ b/src/test/java/cuchaz/enigma/TestMethodDescriptor.java @@ -0,0 +1,247 @@ +/******************************************************************************* + * 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; + +import cuchaz.enigma.mapping.MethodDescriptor; +import cuchaz.enigma.mapping.TypeDescriptor; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +public class TestMethodDescriptor { + + @Test + public void easiest() { + final MethodDescriptor sig = new MethodDescriptor("()V"); + assertThat(sig.getArgumentDescs(), is(empty())); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + + @Test + public void primitives() { + { + final MethodDescriptor sig = new MethodDescriptor("(I)V"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(I)I"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("I"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(IBCJ)Z"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("B"), + new TypeDescriptor("C"), + new TypeDescriptor("J") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); + } + } + + @Test + public void classes() { + { + final MethodDescriptor sig = new MethodDescriptor("([LFoo;)V"); + assertThat(sig.getArgumentDescs().size(), is(1)); + assertThat(sig.getArgumentDescs().get(0), is(new TypeDescriptor("[LFoo;"))); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(LFoo;)LBar;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(LFoo;LMoo;LZoo;)LBar;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;"), + new TypeDescriptor("LMoo;"), + new TypeDescriptor("LZoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LBar;"))); + } + } + + @Test + public void arrays() { + { + final MethodDescriptor sig = new MethodDescriptor("([I)V"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("([I)[J"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[J"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("([I[Z[F)[D"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[I"), + new TypeDescriptor("[Z"), + new TypeDescriptor("[F") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[D"))); + } + } + + @Test + public void mixed() { + { + final MethodDescriptor sig = new MethodDescriptor("(I[JLFoo;)Z"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("[J"), + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("Z"))); + } + { + final MethodDescriptor sig = new MethodDescriptor("(III)[LFoo;"); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("I"), + new TypeDescriptor("I") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[LFoo;"))); + } + } + + @Test + public void replaceClasses() { + { + final MethodDescriptor oldSig = new MethodDescriptor("()V"); + final MethodDescriptor sig = oldSig.remap(s -> null); + assertThat(sig.getArgumentDescs(), is(empty())); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(IJLFoo;)V"); + final MethodDescriptor sig = oldSig.remap(s -> null); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("I"), + new TypeDescriptor("J"), + new TypeDescriptor("LFoo;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("V"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Foo")) { + return "Bar"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LBar;"), + new TypeDescriptor("LBar;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LMoo;"))); + } + { + final MethodDescriptor oldSig = new MethodDescriptor("(LFoo;LBar;)LMoo;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Moo")) { + return "Cow"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("LFoo;"), + new TypeDescriptor("LBar;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("LCow;"))); + } + } + + @Test + public void replaceArrayClasses() { + { + final MethodDescriptor oldSig = new MethodDescriptor("([LFoo;)[[[LBar;"); + final MethodDescriptor sig = oldSig.remap(s -> { + if (s.equals("Foo")) { + return "Food"; + } else if (s.equals("Bar")) { + return "Beer"; + } + return null; + }); + assertThat(sig.getArgumentDescs(), contains( + new TypeDescriptor("[LFood;") + )); + assertThat(sig.getReturnDesc(), is(new TypeDescriptor("[[[LBeer;"))); + } + } + + @Test + public void equals() { + + // base + assertThat(new MethodDescriptor("()V"), is(new MethodDescriptor("()V"))); + + // arguments + assertThat(new MethodDescriptor("(I)V"), is(new MethodDescriptor("(I)V"))); + assertThat(new MethodDescriptor("(ZIZ)V"), is(new MethodDescriptor("(ZIZ)V"))); + assertThat(new MethodDescriptor("(LFoo;)V"), is(new MethodDescriptor("(LFoo;)V"))); + assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(new MethodDescriptor("(LFoo;LBar;)V"))); + assertThat(new MethodDescriptor("([I)V"), is(new MethodDescriptor("([I)V"))); + assertThat(new MethodDescriptor("([[D[[[J)V"), is(new MethodDescriptor("([[D[[[J)V"))); + + assertThat(new MethodDescriptor("()V"), is(not(new MethodDescriptor("(I)V")))); + assertThat(new MethodDescriptor("(I)V"), is(not(new MethodDescriptor("()V")))); + assertThat(new MethodDescriptor("(IJ)V"), is(not(new MethodDescriptor("(JI)V")))); + assertThat(new MethodDescriptor("([[Z)V"), is(not(new MethodDescriptor("([[LFoo;)V")))); + assertThat(new MethodDescriptor("(LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); + assertThat(new MethodDescriptor("([LFoo;LBar;)V"), is(not(new MethodDescriptor("(LFoo;LCow;)V")))); + + // return desc + assertThat(new MethodDescriptor("()I"), is(new MethodDescriptor("()I"))); + assertThat(new MethodDescriptor("()Z"), is(new MethodDescriptor("()Z"))); + assertThat(new MethodDescriptor("()[D"), is(new MethodDescriptor("()[D"))); + assertThat(new MethodDescriptor("()[[[Z"), is(new MethodDescriptor("()[[[Z"))); + assertThat(new MethodDescriptor("()LFoo;"), is(new MethodDescriptor("()LFoo;"))); + assertThat(new MethodDescriptor("()[LFoo;"), is(new MethodDescriptor("()[LFoo;"))); + + assertThat(new MethodDescriptor("()I"), is(not(new MethodDescriptor("()Z")))); + assertThat(new MethodDescriptor("()Z"), is(not(new MethodDescriptor("()I")))); + assertThat(new MethodDescriptor("()[D"), is(not(new MethodDescriptor("()[J")))); + assertThat(new MethodDescriptor("()[[[Z"), is(not(new MethodDescriptor("()[[Z")))); + assertThat(new MethodDescriptor("()LFoo;"), is(not(new MethodDescriptor("()LBar;")))); + assertThat(new MethodDescriptor("()[LFoo;"), is(not(new MethodDescriptor("()[LBar;")))); + } + + @Test + public void testToString() { + assertThat(new MethodDescriptor("()V").toString(), is("()V")); + assertThat(new MethodDescriptor("(I)V").toString(), is("(I)V")); + assertThat(new MethodDescriptor("(ZIZ)V").toString(), is("(ZIZ)V")); + assertThat(new MethodDescriptor("(LFoo;)V").toString(), is("(LFoo;)V")); + assertThat(new MethodDescriptor("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V")); + assertThat(new MethodDescriptor("([I)V").toString(), is("([I)V")); + assertThat(new MethodDescriptor("([[D[[[J)V").toString(), is("([[D[[[J)V")); + } +} diff --git a/src/test/java/cuchaz/enigma/TestSignature.java b/src/test/java/cuchaz/enigma/TestSignature.java deleted file mode 100644 index 534b43a..0000000 --- a/src/test/java/cuchaz/enigma/TestSignature.java +++ /dev/null @@ -1,270 +0,0 @@ -/******************************************************************************* - * 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; - -import cuchaz.enigma.mapping.ClassNameReplacer; -import cuchaz.enigma.mapping.Signature; -import cuchaz.enigma.mapping.Type; -import org.junit.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; - -public class TestSignature { - - @Test - public void easiest() { - final Signature sig = new Signature("()V"); - assertThat(sig.getArgumentTypes(), is(empty())); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - - @Test - public void primitives() { - { - final Signature sig = new Signature("(I)V"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I") - )); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature sig = new Signature("(I)I"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I") - )); - assertThat(sig.getReturnType(), is(new Type("I"))); - } - { - final Signature sig = new Signature("(IBCJ)Z"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("B"), - new Type("C"), - new Type("J") - )); - assertThat(sig.getReturnType(), is(new Type("Z"))); - } - } - - @Test - public void classes() { - { - final Signature sig = new Signature("([LFoo;)V"); - assertThat(sig.getArgumentTypes().size(), is(1)); - assertThat(sig.getArgumentTypes().get(0), is(new Type("[LFoo;"))); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature sig = new Signature("(LFoo;)LBar;"); - assertThat(sig.getArgumentTypes(), contains( - new Type("LFoo;") - )); - assertThat(sig.getReturnType(), is(new Type("LBar;"))); - } - { - final Signature sig = new Signature("(LFoo;LMoo;LZoo;)LBar;"); - assertThat(sig.getArgumentTypes(), contains( - new Type("LFoo;"), - new Type("LMoo;"), - new Type("LZoo;") - )); - assertThat(sig.getReturnType(), is(new Type("LBar;"))); - } - } - - @Test - public void arrays() { - { - final Signature sig = new Signature("([I)V"); - assertThat(sig.getArgumentTypes(), contains( - new Type("[I") - )); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature sig = new Signature("([I)[J"); - assertThat(sig.getArgumentTypes(), contains( - new Type("[I") - )); - assertThat(sig.getReturnType(), is(new Type("[J"))); - } - { - final Signature sig = new Signature("([I[Z[F)[D"); - assertThat(sig.getArgumentTypes(), contains( - new Type("[I"), - new Type("[Z"), - new Type("[F") - )); - assertThat(sig.getReturnType(), is(new Type("[D"))); - } - } - - @Test - public void mixed() { - { - final Signature sig = new Signature("(I[JLFoo;)Z"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("[J"), - new Type("LFoo;") - )); - assertThat(sig.getReturnType(), is(new Type("Z"))); - } - { - final Signature sig = new Signature("(III)[LFoo;"); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("I"), - new Type("I") - )); - assertThat(sig.getReturnType(), is(new Type("[LFoo;"))); - } - } - - @Test - public void replaceClasses() { - { - final Signature oldSig = new Signature("()V"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - return null; - } - }); - assertThat(sig.getArgumentTypes(), is(empty())); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature oldSig = new Signature("(IJLFoo;)V"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("I"), - new Type("J"), - new Type("LFoo;") - )); - assertThat(sig.getReturnType(), is(new Type("V"))); - } - { - final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - if (val.equals("Foo")) { - return "Bar"; - } - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("LBar;"), - new Type("LBar;") - )); - assertThat(sig.getReturnType(), is(new Type("LMoo;"))); - } - { - final Signature oldSig = new Signature("(LFoo;LBar;)LMoo;"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - if (val.equals("Moo")) { - return "Cow"; - } - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("LFoo;"), - new Type("LBar;") - )); - assertThat(sig.getReturnType(), is(new Type("LCow;"))); - } - } - - @Test - public void replaceArrayClasses() { - { - final Signature oldSig = new Signature("([LFoo;)[[[LBar;"); - final Signature sig = new Signature(oldSig, new ClassNameReplacer() { - @Override - public String replace(String val) { - if (val.equals("Foo")) { - return "Food"; - } else if (val.equals("Bar")) { - return "Beer"; - } - return null; - } - }); - assertThat(sig.getArgumentTypes(), contains( - new Type("[LFood;") - )); - assertThat(sig.getReturnType(), is(new Type("[[[LBeer;"))); - } - } - - @Test - public void equals() { - - // base - assertThat(new Signature("()V"), is(new Signature("()V"))); - - // arguments - assertThat(new Signature("(I)V"), is(new Signature("(I)V"))); - assertThat(new Signature("(ZIZ)V"), is(new Signature("(ZIZ)V"))); - assertThat(new Signature("(LFoo;)V"), is(new Signature("(LFoo;)V"))); - assertThat(new Signature("(LFoo;LBar;)V"), is(new Signature("(LFoo;LBar;)V"))); - assertThat(new Signature("([I)V"), is(new Signature("([I)V"))); - assertThat(new Signature("([[D[[[J)V"), is(new Signature("([[D[[[J)V"))); - - assertThat(new Signature("()V"), is(not(new Signature("(I)V")))); - assertThat(new Signature("(I)V"), is(not(new Signature("()V")))); - assertThat(new Signature("(IJ)V"), is(not(new Signature("(JI)V")))); - assertThat(new Signature("([[Z)V"), is(not(new Signature("([[LFoo;)V")))); - assertThat(new Signature("(LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); - assertThat(new Signature("([LFoo;LBar;)V"), is(not(new Signature("(LFoo;LCow;)V")))); - - // return type - assertThat(new Signature("()I"), is(new Signature("()I"))); - assertThat(new Signature("()Z"), is(new Signature("()Z"))); - assertThat(new Signature("()[D"), is(new Signature("()[D"))); - assertThat(new Signature("()[[[Z"), is(new Signature("()[[[Z"))); - assertThat(new Signature("()LFoo;"), is(new Signature("()LFoo;"))); - assertThat(new Signature("()[LFoo;"), is(new Signature("()[LFoo;"))); - - assertThat(new Signature("()I"), is(not(new Signature("()Z")))); - assertThat(new Signature("()Z"), is(not(new Signature("()I")))); - assertThat(new Signature("()[D"), is(not(new Signature("()[J")))); - assertThat(new Signature("()[[[Z"), is(not(new Signature("()[[Z")))); - assertThat(new Signature("()LFoo;"), is(not(new Signature("()LBar;")))); - assertThat(new Signature("()[LFoo;"), is(not(new Signature("()[LBar;")))); - } - - @Test - public void testToString() { - assertThat(new Signature("()V").toString(), is("()V")); - assertThat(new Signature("(I)V").toString(), is("(I)V")); - assertThat(new Signature("(ZIZ)V").toString(), is("(ZIZ)V")); - assertThat(new Signature("(LFoo;)V").toString(), is("(LFoo;)V")); - assertThat(new Signature("(LFoo;LBar;)V").toString(), is("(LFoo;LBar;)V")); - assertThat(new Signature("([I)V").toString(), is("([I)V")); - assertThat(new Signature("([[D[[[J)V").toString(), is("([[D[[[J)V")); - } -} diff --git a/src/test/java/cuchaz/enigma/TestTokensConstructors.java b/src/test/java/cuchaz/enigma/TestTokensConstructors.java index e40d5fd..0148f2c 100644 --- a/src/test/java/cuchaz/enigma/TestTokensConstructors.java +++ b/src/test/java/cuchaz/enigma/TestTokensConstructors.java @@ -11,131 +11,127 @@ package cuchaz.enigma; -import cuchaz.enigma.mapping.BehaviorEntry; +import cuchaz.enigma.mapping.MethodEntry; import org.junit.Test; import java.util.jar.JarFile; -import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByConstructor; import static cuchaz.enigma.TestEntryFactory.newBehaviorReferenceByMethod; -import static cuchaz.enigma.TestEntryFactory.newConstructor; +import static cuchaz.enigma.TestEntryFactory.newMethod; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.*; public class TestTokensConstructors extends TokenChecker { public TestTokensConstructors() - throws Exception { + throws Exception { super(new JarFile("build/test-obf/constructors.jar")); } @Test public void baseDeclarations() { - assertThat(getDeclarationToken(newConstructor("a", "()V")), is("a")); - assertThat(getDeclarationToken(newConstructor("a", "(I)V")), is("a")); + assertThat(getDeclarationToken(newMethod("a", "", "()V")), is("a")); + assertThat(getDeclarationToken(newMethod("a", "", "(I)V")), is("a")); } @Test public void subDeclarations() { - assertThat(getDeclarationToken(newConstructor("d", "()V")), is("d")); - assertThat(getDeclarationToken(newConstructor("d", "(I)V")), is("d")); - assertThat(getDeclarationToken(newConstructor("d", "(II)V")), is("d")); - assertThat(getDeclarationToken(newConstructor("d", "(III)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "()V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(I)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(II)V")), is("d")); + assertThat(getDeclarationToken(newMethod("d", "", "(III)V")), is("d")); } @Test public void subsubDeclarations() { - assertThat(getDeclarationToken(newConstructor("e", "(I)V")), is("e")); + assertThat(getDeclarationToken(newMethod("e", "", "(I)V")), is("e")); } @Test public void defaultDeclarations() { - assertThat(getDeclarationToken(newConstructor("c", "()V")), nullValue()); + assertThat(getDeclarationToken(newMethod("c", "", "()V")), nullValue()); } @Test public void baseDefaultReferences() { - BehaviorEntry source = newConstructor("a", "()V"); + MethodEntry source = newMethod("a", "", "()V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "a", "()V")), - containsInAnyOrder("a") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "a", "()V")), + containsInAnyOrder("a") ); assertThat( - getReferenceTokens(newBehaviorReferenceByConstructor(source, "d", "()V")), - is(empty()) // implicit call, not decompiled to token + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "()V")), + is(empty()) // implicit call, not decompiled to token ); assertThat( - getReferenceTokens(newBehaviorReferenceByConstructor(source, "d", "(III)V")), - is(empty()) // implicit call, not decompiled to token + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(III)V")), + is(empty()) // implicit call, not decompiled to token ); } @Test public void baseIntReferences() { - BehaviorEntry source = newConstructor("a", "(I)V"); + MethodEntry source = newMethod("a", "", "(I)V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "b", "()V")), - containsInAnyOrder("a") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "b", "()V")), + containsInAnyOrder("a") ); } @Test public void subDefaultReferences() { - BehaviorEntry source = newConstructor("d", "()V"); + MethodEntry source = newMethod("d", "", "()V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "c", "()V")), - containsInAnyOrder("d") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "c", "()V")), + containsInAnyOrder("d") ); assertThat( - getReferenceTokens(newBehaviorReferenceByConstructor(source, "d", "(I)V")), - containsInAnyOrder("this") + getReferenceTokens(newBehaviorReferenceByMethod(source, "d", "", "(I)V")), + containsInAnyOrder("this") ); } @Test public void subIntReferences() { - BehaviorEntry source = newConstructor("d", "(I)V"); + MethodEntry source = newMethod("d", "", "(I)V"); assertThat(getReferenceTokens( - newBehaviorReferenceByMethod(source, "b", "d", "()V")), - containsInAnyOrder("d") + newBehaviorReferenceByMethod(source, "b", "d", "()V")), + containsInAnyOrder("d") ); assertThat(getReferenceTokens( - newBehaviorReferenceByConstructor(source, "d", "(II)V")), - containsInAnyOrder("this") + newBehaviorReferenceByMethod(source, "d", "", "(II)V")), + containsInAnyOrder("this") ); assertThat(getReferenceTokens( - newBehaviorReferenceByConstructor(source, "e", "(I)V")), - containsInAnyOrder("super") + newBehaviorReferenceByMethod(source, "e", "", "(I)V")), + containsInAnyOrder("super") ); } @Test public void subIntIntReferences() { - BehaviorEntry source = newConstructor("d", "(II)V"); + MethodEntry source = newMethod("d", "", "(II)V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "e", "()V")), - containsInAnyOrder("d") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "e", "()V")), + containsInAnyOrder("d") ); } @Test public void subsubIntReferences() { - BehaviorEntry source = newConstructor("e", "(I)V"); + MethodEntry source = newMethod("e", "", "(I)V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "f", "()V")), - containsInAnyOrder("e") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "f", "()V")), + containsInAnyOrder("e") ); } @Test public void defaultConstructableReferences() { - BehaviorEntry source = newConstructor("c", "()V"); + MethodEntry source = newMethod("c", "", "()V"); assertThat( - getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "g", "()V")), - containsInAnyOrder("c") + getReferenceTokens(newBehaviorReferenceByMethod(source, "b", "g", "()V")), + containsInAnyOrder("c") ); } } diff --git a/src/test/java/cuchaz/enigma/TestTypeDescriptor.java b/src/test/java/cuchaz/enigma/TestTypeDescriptor.java new file mode 100644 index 0000000..b874f62 --- /dev/null +++ b/src/test/java/cuchaz/enigma/TestTypeDescriptor.java @@ -0,0 +1,243 @@ +/******************************************************************************* + * 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; + +import cuchaz.enigma.mapping.TypeDescriptor; +import org.junit.Test; + +import static cuchaz.enigma.TestEntryFactory.newClass; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +public class TestTypeDescriptor { + + @Test + public void isVoid() { + assertThat(new TypeDescriptor("V").isVoid(), is(true)); + assertThat(new TypeDescriptor("Z").isVoid(), is(false)); + assertThat(new TypeDescriptor("B").isVoid(), is(false)); + assertThat(new TypeDescriptor("C").isVoid(), is(false)); + assertThat(new TypeDescriptor("I").isVoid(), is(false)); + assertThat(new TypeDescriptor("J").isVoid(), is(false)); + assertThat(new TypeDescriptor("F").isVoid(), is(false)); + assertThat(new TypeDescriptor("D").isVoid(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isVoid(), is(false)); + assertThat(new TypeDescriptor("[I").isVoid(), is(false)); + } + + @Test + public void isPrimitive() { + assertThat(new TypeDescriptor("V").isPrimitive(), is(false)); + assertThat(new TypeDescriptor("Z").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("B").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("C").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("I").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("J").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("F").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("D").isPrimitive(), is(true)); + assertThat(new TypeDescriptor("LFoo;").isPrimitive(), is(false)); + assertThat(new TypeDescriptor("[I").isPrimitive(), is(false)); + } + + @Test + public void getPrimitive() { + assertThat(new TypeDescriptor("Z").getPrimitive(), is(TypeDescriptor.Primitive.Boolean)); + assertThat(new TypeDescriptor("B").getPrimitive(), is(TypeDescriptor.Primitive.Byte)); + assertThat(new TypeDescriptor("C").getPrimitive(), is(TypeDescriptor.Primitive.Character)); + assertThat(new TypeDescriptor("I").getPrimitive(), is(TypeDescriptor.Primitive.Integer)); + assertThat(new TypeDescriptor("J").getPrimitive(), is(TypeDescriptor.Primitive.Long)); + assertThat(new TypeDescriptor("F").getPrimitive(), is(TypeDescriptor.Primitive.Float)); + assertThat(new TypeDescriptor("D").getPrimitive(), is(TypeDescriptor.Primitive.Double)); + } + + @Test + public void isClass() { + assertThat(new TypeDescriptor("V").isType(), is(false)); + assertThat(new TypeDescriptor("Z").isType(), is(false)); + assertThat(new TypeDescriptor("B").isType(), is(false)); + assertThat(new TypeDescriptor("C").isType(), is(false)); + assertThat(new TypeDescriptor("I").isType(), is(false)); + assertThat(new TypeDescriptor("J").isType(), is(false)); + assertThat(new TypeDescriptor("F").isType(), is(false)); + assertThat(new TypeDescriptor("D").isType(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isType(), is(true)); + assertThat(new TypeDescriptor("[I").isType(), is(false)); + } + + @Test + public void getClassEntry() { + assertThat(new TypeDescriptor("LFoo;").getOwnerEntry(), is(newClass("Foo"))); + assertThat(new TypeDescriptor("Ljava/lang/String;").getOwnerEntry(), is(newClass("java/lang/String"))); + } + + @Test + public void getArrayClassEntry() { + assertThat(new TypeDescriptor("[LFoo;").getOwnerEntry(), is(newClass("Foo"))); + assertThat(new TypeDescriptor("[[[Ljava/lang/String;").getOwnerEntry(), is(newClass("java/lang/String"))); + } + + @Test + public void isArray() { + assertThat(new TypeDescriptor("V").isArray(), is(false)); + assertThat(new TypeDescriptor("Z").isArray(), is(false)); + assertThat(new TypeDescriptor("B").isArray(), is(false)); + assertThat(new TypeDescriptor("C").isArray(), is(false)); + assertThat(new TypeDescriptor("I").isArray(), is(false)); + assertThat(new TypeDescriptor("J").isArray(), is(false)); + assertThat(new TypeDescriptor("F").isArray(), is(false)); + assertThat(new TypeDescriptor("D").isArray(), is(false)); + assertThat(new TypeDescriptor("LFoo;").isArray(), is(false)); + assertThat(new TypeDescriptor("[I").isArray(), is(true)); + } + + @Test + public void getArrayDimension() { + assertThat(new TypeDescriptor("[I").getArrayDimension(), is(1)); + assertThat(new TypeDescriptor("[[I").getArrayDimension(), is(2)); + assertThat(new TypeDescriptor("[[[I").getArrayDimension(), is(3)); + } + + @Test + public void getArrayType() { + assertThat(new TypeDescriptor("[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[[[I").getArrayType(), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("[Ljava/lang/String;").getArrayType(), is(new TypeDescriptor("Ljava/lang/String;"))); + } + + @Test + public void hasClass() { + assertThat(new TypeDescriptor("LFoo;").containsType(), is(true)); + assertThat(new TypeDescriptor("Ljava/lang/String;").containsType(), is(true)); + assertThat(new TypeDescriptor("[LBar;").containsType(), is(true)); + assertThat(new TypeDescriptor("[[[LCat;").containsType(), is(true)); + + assertThat(new TypeDescriptor("V").containsType(), is(false)); + assertThat(new TypeDescriptor("[I").containsType(), is(false)); + assertThat(new TypeDescriptor("[[[I").containsType(), is(false)); + assertThat(new TypeDescriptor("Z").containsType(), is(false)); + } + + @Test + public void parseVoid() { + final String answer = "V"; + assertThat(TypeDescriptor.parseFirst("V"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VVV"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VIJ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("V[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("VLFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("V[LFoo;"), is(answer)); + } + + @Test + public void parsePrimitive() { + final String answer = "I"; + assertThat(TypeDescriptor.parseFirst("I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("ILFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("I[LFoo;"), is(answer)); + } + + @Test + public void parseClass() { + { + final String answer = "LFoo;"; + assertThat(TypeDescriptor.parseFirst("LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("LFoo;[LFoo;"), is(answer)); + } + { + final String answer = "Ljava/lang/String;"; + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("Ljava/lang/String;[LFoo;"), is(answer)); + } + } + + @Test + public void parseArray() { + { + final String answer = "[I"; + assertThat(TypeDescriptor.parseFirst("[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[ILFoo;"), is(answer)); + } + { + final String answer = "[[I"; + assertThat(TypeDescriptor.parseFirst("[[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[III"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[IJZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[I[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[[ILFoo;"), is(answer)); + } + { + final String answer = "[LFoo;"; + assertThat(TypeDescriptor.parseFirst("[LFoo;"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;II"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;JZ"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;[I"), is(answer)); + assertThat(TypeDescriptor.parseFirst("[LFoo;LFoo;"), is(answer)); + } + } + + @Test + public void equals() { + assertThat(new TypeDescriptor("V"), is(new TypeDescriptor("V"))); + assertThat(new TypeDescriptor("Z"), is(new TypeDescriptor("Z"))); + assertThat(new TypeDescriptor("B"), is(new TypeDescriptor("B"))); + assertThat(new TypeDescriptor("C"), is(new TypeDescriptor("C"))); + assertThat(new TypeDescriptor("I"), is(new TypeDescriptor("I"))); + assertThat(new TypeDescriptor("J"), is(new TypeDescriptor("J"))); + assertThat(new TypeDescriptor("F"), is(new TypeDescriptor("F"))); + assertThat(new TypeDescriptor("D"), is(new TypeDescriptor("D"))); + assertThat(new TypeDescriptor("LFoo;"), is(new TypeDescriptor("LFoo;"))); + assertThat(new TypeDescriptor("[I"), is(new TypeDescriptor("[I"))); + assertThat(new TypeDescriptor("[[[I"), is(new TypeDescriptor("[[[I"))); + assertThat(new TypeDescriptor("[LFoo;"), is(new TypeDescriptor("[LFoo;"))); + + assertThat(new TypeDescriptor("V"), is(not(new TypeDescriptor("I")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("J")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("LBar;")))); + assertThat(new TypeDescriptor("I"), is(not(new TypeDescriptor("[I")))); + assertThat(new TypeDescriptor("LFoo;"), is(not(new TypeDescriptor("LBar;")))); + assertThat(new TypeDescriptor("[I"), is(not(new TypeDescriptor("[Z")))); + assertThat(new TypeDescriptor("[[[I"), is(not(new TypeDescriptor("[I")))); + assertThat(new TypeDescriptor("[LFoo;"), is(not(new TypeDescriptor("[LBar;")))); + } + + @Test + public void testToString() { + assertThat(new TypeDescriptor("V").toString(), is("V")); + assertThat(new TypeDescriptor("Z").toString(), is("Z")); + assertThat(new TypeDescriptor("B").toString(), is("B")); + assertThat(new TypeDescriptor("C").toString(), is("C")); + assertThat(new TypeDescriptor("I").toString(), is("I")); + assertThat(new TypeDescriptor("J").toString(), is("J")); + assertThat(new TypeDescriptor("F").toString(), is("F")); + assertThat(new TypeDescriptor("D").toString(), is("D")); + assertThat(new TypeDescriptor("LFoo;").toString(), is("LFoo;")); + assertThat(new TypeDescriptor("[I").toString(), is("[I")); + assertThat(new TypeDescriptor("[[[I").toString(), is("[[[I")); + assertThat(new TypeDescriptor("[LFoo;").toString(), is("[LFoo;")); + } +} -- cgit v1.2.3