From 58c0aeb15a65324de08a914dfa62cc68a516a4e3 Mon Sep 17 00:00:00 2001 From: Runemoro Date: Mon, 9 Mar 2020 06:04:08 -0400 Subject: CFR support (#192) * Add decompiler API * Add CFR support--- .../cuchaz/enigma/CachingClasspathTypeLoader.java | 33 -- src/main/java/cuchaz/enigma/CachingTypeLoader.java | 38 -- src/main/java/cuchaz/enigma/ClassProvider.java | 10 + src/main/java/cuchaz/enigma/CompiledSource.java | 10 - .../cuchaz/enigma/CompiledSourceTypeLoader.java | 139 ------- src/main/java/cuchaz/enigma/EnigmaProject.java | 39 +- .../java/cuchaz/enigma/NoRetryMetadataSystem.java | 38 -- src/main/java/cuchaz/enigma/SourceProvider.java | 119 ------ .../java/cuchaz/enigma/SynchronizedTypeLoader.java | 20 - .../enigma/analysis/AddJavadocsAstTransform.java | 135 ------- .../java/cuchaz/enigma/analysis/BuiltinPlugin.java | 14 +- .../java/cuchaz/enigma/analysis/ClassCache.java | 4 +- .../enigma/analysis/DropImportAstTransform.java | 33 -- .../analysis/DropVarModifiersAstTransform.java | 37 -- .../java/cuchaz/enigma/analysis/SourceIndex.java | 240 ------------ .../enigma/analysis/SourceIndexClassVisitor.java | 96 ----- .../enigma/analysis/SourceIndexMethodVisitor.java | 216 ----------- .../cuchaz/enigma/analysis/SourceIndexVisitor.java | 38 -- src/main/java/cuchaz/enigma/analysis/Token.java | 6 +- .../cuchaz/enigma/analysis/TreeDumpVisitor.java | 93 ----- .../cuchaz/enigma/command/DecompileCommand.java | 25 +- src/main/java/cuchaz/enigma/config/Config.java | 20 +- .../cuchaz/enigma/gui/DecompiledClassSource.java | 5 +- src/main/java/cuchaz/enigma/gui/GuiController.java | 87 +++-- src/main/java/cuchaz/enigma/gui/RefreshMode.java | 7 + .../java/cuchaz/enigma/gui/elements/MenuBar.java | 22 ++ src/main/java/cuchaz/enigma/source/Decompiler.java | 5 + .../cuchaz/enigma/source/DecompilerService.java | 11 + .../java/cuchaz/enigma/source/Decompilers.java | 9 + src/main/java/cuchaz/enigma/source/Source.java | 11 + .../java/cuchaz/enigma/source/SourceIndex.java | 174 +++++++++ .../java/cuchaz/enigma/source/SourceSettings.java | 11 + .../cuchaz/enigma/source/cfr/CfrDecompiler.java | 108 ++++++ .../java/cuchaz/enigma/source/cfr/CfrSource.java | 38 ++ .../cuchaz/enigma/source/cfr/EnigmaDumper.java | 427 +++++++++++++++++++++ .../cuchaz/enigma/source/procyon/EntryParser.java | 49 +++ .../enigma/source/procyon/ProcyonDecompiler.java | 81 ++++ .../enigma/source/procyon/ProcyonSource.java | 49 +++ .../procyon/index/SourceIndexClassVisitor.java | 95 +++++ .../procyon/index/SourceIndexMethodVisitor.java | 218 +++++++++++ .../source/procyon/index/SourceIndexVisitor.java | 40 ++ .../enigma/source/procyon/index/TokenFactory.java | 41 ++ .../transformers/AddJavadocsAstTransform.java | 134 +++++++ .../transformers/DropImportAstTransform.java | 33 ++ .../transformers/DropVarModifiersAstTransform.java | 37 ++ .../procyon/transformers/InvalidIdentifierFix.java | 29 ++ .../source/procyon/transformers/Java8Generics.java | 107 ++++++ .../ObfuscatedEnumSwitchRewriterTransform.java | 414 ++++++++++++++++++++ .../procyon/transformers/RemoveObjectCasts.java | 39 ++ .../source/procyon/transformers/VarargsFixer.java | 197 ++++++++++ .../typeloader/CachingClasspathTypeLoader.java | 33 ++ .../procyon/typeloader/CachingTypeLoader.java | 38 ++ .../typeloader/CompiledSourceTypeLoader.java | 140 +++++++ .../procyon/typeloader/NoRetryMetadataSystem.java | 38 ++ .../procyon/typeloader/SynchronizedTypeLoader.java | 20 + .../translation/representation/TypeDescriptor.java | 5 - .../representation/entry/ClassDefEntry.java | 10 - .../representation/entry/ClassEntry.java | 5 - .../representation/entry/FieldDefEntry.java | 9 - .../representation/entry/MethodDefEntry.java | 9 - src/main/java/cuchaz/enigma/utils/Pair.java | 26 ++ 61 files changed, 2807 insertions(+), 1407 deletions(-) delete mode 100644 src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/CachingTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/ClassProvider.java delete mode 100644 src/main/java/cuchaz/enigma/CompiledSource.java delete mode 100644 src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java delete mode 100644 src/main/java/cuchaz/enigma/SourceProvider.java delete mode 100644 src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/AddJavadocsAstTransform.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/DropVarModifiersAstTransform.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndex.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java delete mode 100644 src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java create mode 100644 src/main/java/cuchaz/enigma/gui/RefreshMode.java create mode 100644 src/main/java/cuchaz/enigma/source/Decompiler.java create mode 100644 src/main/java/cuchaz/enigma/source/DecompilerService.java create mode 100644 src/main/java/cuchaz/enigma/source/Decompilers.java create mode 100644 src/main/java/cuchaz/enigma/source/Source.java create mode 100644 src/main/java/cuchaz/enigma/source/SourceIndex.java create mode 100644 src/main/java/cuchaz/enigma/source/SourceSettings.java create mode 100644 src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java create mode 100644 src/main/java/cuchaz/enigma/source/cfr/CfrSource.java create mode 100644 src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/EntryParser.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/utils/Pair.java (limited to 'src/main/java/cuchaz') diff --git a/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java deleted file mode 100644 index b2aed84..0000000 --- a/src/main/java/cuchaz/enigma/CachingClasspathTypeLoader.java +++ /dev/null @@ -1,33 +0,0 @@ -package cuchaz.enigma; - -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ClasspathTypeLoader; -import com.strobel.assembler.metadata.ITypeLoader; - -/** - * Caching version of {@link ClasspathTypeLoader} - */ -public class CachingClasspathTypeLoader extends CachingTypeLoader { - private static ITypeLoader extraClassPathLoader = null; - - public static void setExtraClassPathLoader(ITypeLoader loader){ - extraClassPathLoader = loader; - } - - private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); - - @Override - protected byte[] doLoad(String className) { - Buffer parentBuf = new Buffer(); - if (classpathLoader.tryLoadType(className, parentBuf)) { - return parentBuf.array(); - } - if (extraClassPathLoader != null){ - parentBuf.reset(); - if (extraClassPathLoader.tryLoadType(className, parentBuf)){ - return parentBuf.array(); - } - } - return EMPTY_ARRAY;//need to return *something* as null means no store - } -} diff --git a/src/main/java/cuchaz/enigma/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/CachingTypeLoader.java deleted file mode 100644 index 22c31c6..0000000 --- a/src/main/java/cuchaz/enigma/CachingTypeLoader.java +++ /dev/null @@ -1,38 +0,0 @@ -package cuchaz.enigma; - -import com.google.common.collect.Maps; -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; - -import java.util.Map; - -/** - * Common cache functions - */ -public abstract class CachingTypeLoader implements ITypeLoader { - protected static final byte[] EMPTY_ARRAY = {}; - - private final Map cache = Maps.newHashMap(); - - protected abstract byte[] doLoad(String className); - - @Override - public boolean tryLoadType(String className, Buffer out) { - - // check the cache - byte[] data = this.cache.computeIfAbsent(className, this::doLoad); - - if (data == EMPTY_ARRAY) { - return false; - } - - out.reset(data.length); - System.arraycopy(data, 0, out.array(), out.position(), data.length); - out.position(0); - return true; - } - - public void clearCache() { - this.cache.clear(); - } -} diff --git a/src/main/java/cuchaz/enigma/ClassProvider.java b/src/main/java/cuchaz/enigma/ClassProvider.java new file mode 100644 index 0000000..2b91379 --- /dev/null +++ b/src/main/java/cuchaz/enigma/ClassProvider.java @@ -0,0 +1,10 @@ +package cuchaz.enigma; + +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; + +public interface ClassProvider { + @Nullable + ClassNode getClassNode(String name); +} diff --git a/src/main/java/cuchaz/enigma/CompiledSource.java b/src/main/java/cuchaz/enigma/CompiledSource.java deleted file mode 100644 index fc051d3..0000000 --- a/src/main/java/cuchaz/enigma/CompiledSource.java +++ /dev/null @@ -1,10 +0,0 @@ -package cuchaz.enigma; - -import org.objectweb.asm.tree.ClassNode; - -import javax.annotation.Nullable; - -public interface CompiledSource { - @Nullable - ClassNode getClassNode(String name); -} diff --git a/src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java deleted file mode 100644 index c746abe..0000000 --- a/src/main/java/cuchaz/enigma/CompiledSourceTypeLoader.java +++ /dev/null @@ -1,139 +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 com.google.common.collect.Lists; -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.function.Function; - -public class CompiledSourceTypeLoader extends CachingTypeLoader { - //Store one instance as the classpath shouldn't change during load - private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); - - private final CompiledSource compiledSource; - private final LinkedList> visitors = new LinkedList<>(); - - public CompiledSourceTypeLoader(CompiledSource compiledSource) { - this.compiledSource = compiledSource; - } - - public void addVisitor(Function visitor) { - this.visitors.addFirst(visitor); - } - - @Override - protected byte[] doLoad(String className) { - byte[] data = loadType(className); - if (data == null) { - return loadClasspath(className); - } - - return data; - } - - private byte[] loadClasspath(String name) { - Buffer parentBuf = new Buffer(); - if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { - return parentBuf.array(); - } - return EMPTY_ARRAY; - } - - private byte[] loadType(String className) { - ClassEntry entry = new ClassEntry(className); - - // find the class in the jar - ClassNode node = findClassNode(entry); - if (node == null) { - // couldn't find it - return null; - } - - removeRedundantClassCalls(node); - - ClassWriter writer = new ClassWriter(0); - - ClassVisitor visitor = writer; - for (Function visitorFunction : this.visitors) { - visitor = visitorFunction.apply(visitor); - } - - node.accept(visitor); - - // we have a transformed class! - return writer.toByteArray(); - } - - private void removeRedundantClassCalls(ClassNode node) { - // remove .getClass() calls that are seemingly injected - // DUP - // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; - // POP - for (MethodNode methodNode : node.methods) { - AbstractInsnNode insnNode = methodNode.instructions.getFirst(); - while (insnNode != null) { - if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { - MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; - if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { - AbstractInsnNode previous = methodInsnNode.getPrevious(); - AbstractInsnNode next = methodInsnNode.getNext(); - if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { - insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction - methodNode.instructions.remove(previous); - methodNode.instructions.remove(methodInsnNode); - methodNode.instructions.remove(next); - } - } - } - insnNode = insnNode.getNext(); - } - } - } - - private ClassNode findClassNode(ClassEntry entry) { - // try to find the class in the jar - for (String className : getClassNamesToTry(entry)) { - ClassNode node = compiledSource.getClassNode(className); - if (node != null) { - return node; - } - } - - // didn't find it ;_; - return null; - } - - private Collection getClassNamesToTry(ClassEntry entry) { - List classNamesToTry = Lists.newArrayList(); - classNamesToTry.add(entry.getFullName()); - - ClassEntry outerClass = entry.getOuterClass(); - if (outerClass != null) { - classNamesToTry.addAll(getClassNamesToTry(outerClass)); - } - - return classNamesToTry; - } -} diff --git a/src/main/java/cuchaz/enigma/EnigmaProject.java b/src/main/java/cuchaz/enigma/EnigmaProject.java index fddaccc..78b49b5 100644 --- a/src/main/java/cuchaz/enigma/EnigmaProject.java +++ b/src/main/java/cuchaz/enigma/EnigmaProject.java @@ -1,16 +1,13 @@ package cuchaz.enigma; import com.google.common.base.Functions; -import com.strobel.assembler.metadata.ITypeLoader; -import com.strobel.assembler.metadata.MetadataSystem; -import com.strobel.decompiler.DecompilerSettings; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.api.service.NameProposalService; import cuchaz.enigma.bytecode.translators.SourceFixVisitor; import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; +import cuchaz.enigma.source.*; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.*; import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; @@ -25,10 +22,7 @@ import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; @@ -200,7 +194,7 @@ public class EnigmaProject { } } - public SourceExport decompile(ProgressListener progress) { + public SourceExport decompile(ProgressListener progress, DecompilerService decompilerService) { Collection classes = this.compiled.values().stream() .filter(classNode -> classNode.name.indexOf('$') == -1) .collect(Collectors.toList()); @@ -208,18 +202,7 @@ public class EnigmaProject { progress.init(classes.size(), I18n.translate("progress.classes.decompiling")); //create a common instance outside the loop as mappings shouldn't be changing while this is happening - CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(this.compiled::get); - - //synchronized to make sure the parallelStream doesn't CME with the cache - ITypeLoader synchronizedTypeLoader = new SynchronizedTypeLoader(typeLoader); - - MetadataSystem metadataSystem = new NoRetryMetadataSystem(synchronizedTypeLoader); - - //ensures methods are loaded on classload and prevents race conditions - metadataSystem.setEagerMethodLoadingEnabled(true); - - DecompilerSettings settings = SourceProvider.createSettings(); - SourceProvider sourceProvider = new SourceProvider(settings, synchronizedTypeLoader, metadataSystem); + Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false)); AtomicInteger count = new AtomicInteger(); @@ -227,7 +210,7 @@ public class EnigmaProject { .map(translatedNode -> { progress.step(count.getAndIncrement(), translatedNode.name); - String source = decompileClass(translatedNode, sourceProvider); + String source = decompileClass(translatedNode, decompiler); return new ClassSource(translatedNode.name, source); }) .collect(Collectors.toList()); @@ -235,16 +218,8 @@ public class EnigmaProject { return new SourceExport(decompiled); } - private String decompileClass(ClassNode translatedNode, SourceProvider sourceProvider) { - StringWriter writer = new StringWriter(); - try { - CompilationUnit sourceTree = sourceProvider.getSources(translatedNode.name); - sourceProvider.writeSource(writer, sourceTree); - } catch (Throwable t) { - t.printStackTrace(); - t.printStackTrace(new PrintWriter(writer)); - } - return writer.toString(); + private String decompileClass(ClassNode translatedNode, Decompiler decompiler) { + return decompiler.getSource(translatedNode.name).asString(); } } diff --git a/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java deleted file mode 100644 index 269d31e..0000000 --- a/src/main/java/cuchaz/enigma/NoRetryMetadataSystem.java +++ /dev/null @@ -1,38 +0,0 @@ -package cuchaz.enigma; - -import com.strobel.assembler.metadata.ITypeLoader; -import com.strobel.assembler.metadata.MetadataSystem; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; - -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public final class NoRetryMetadataSystem extends MetadataSystem { - private final Set failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); - - public NoRetryMetadataSystem(final ITypeLoader typeLoader) { - super(typeLoader); - } - - @Override - protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { - if (failedTypes.contains(descriptor)) { - return null; - } - - final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); - - if (result == null) { - failedTypes.add(descriptor); - } - - return result; - } - - @Override - public synchronized TypeDefinition resolve(final TypeReference type) { - return super.resolve(type); - } -} diff --git a/src/main/java/cuchaz/enigma/SourceProvider.java b/src/main/java/cuchaz/enigma/SourceProvider.java deleted file mode 100644 index d3d3003..0000000 --- a/src/main/java/cuchaz/enigma/SourceProvider.java +++ /dev/null @@ -1,119 +0,0 @@ -package cuchaz.enigma; - -import com.strobel.assembler.metadata.ITypeLoader; -import com.strobel.assembler.metadata.MetadataSystem; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.DecompilerContext; -import com.strobel.decompiler.DecompilerSettings; -import com.strobel.decompiler.PlainTextOutput; -import com.strobel.decompiler.languages.java.BraceStyle; -import com.strobel.decompiler.languages.java.JavaFormattingOptions; -import com.strobel.decompiler.languages.java.JavaOutputVisitor; -import com.strobel.decompiler.languages.java.ast.AstBuilder; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; -import cuchaz.enigma.utils.Utils; -import oml.ast.transformers.*; - -import java.io.StringWriter; -import java.io.Writer; -import java.lang.ref.WeakReference; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; - -public class SourceProvider { - private final DecompilerSettings settings; - - private final ITypeLoader typeLoader; - private final MetadataSystem metadataSystem; - - private String lastLookUpName; - private WeakReference lastDecompiled; - - public SourceProvider(DecompilerSettings settings, ITypeLoader typeLoader, MetadataSystem metadataSystem) { - this.settings = settings; - this.typeLoader = typeLoader; - this.metadataSystem = metadataSystem; - } - - public SourceProvider(DecompilerSettings settings, ITypeLoader typeLoader) { - this(settings, typeLoader, new NoRetryMetadataSystem(typeLoader)); - } - - public static DecompilerSettings createSettings() { - DecompilerSettings settings = DecompilerSettings.javaDefaults(); - settings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); - settings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); - settings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); - settings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); - settings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); - - JavaFormattingOptions formattingOptions = settings.getJavaFormattingOptions(); - formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine; - formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine; - formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine; - - return settings; - } - - public CompilationUnit getSources(String name) { - // Optimization for javadoc-caused decompilations - if (Objects.equals(lastLookUpName, name)) { - CompilationUnit last = lastDecompiled.get(); - if (last != null) - return last; - } - - TypeReference type = metadataSystem.lookupType(name); - if (type == null) { - throw new Error(String.format("Unable to find desc: %s", name)); - } - - TypeDefinition resolvedType = type.resolve(); - - settings.setTypeLoader(typeLoader); - - // decompile it! - DecompilerContext context = new DecompilerContext(); - context.setCurrentType(resolvedType); - context.setSettings(settings); - - AstBuilder builder = new AstBuilder(context); - builder.addType(resolvedType); - builder.runTransformations(null); - runCustomTransforms(builder, context); - - CompilationUnit ret = builder.getCompilationUnit(); - lastLookUpName = name; - lastDecompiled = new WeakReference<>(ret); - return ret; - } - - public void writeSource(Writer writer, CompilationUnit sourceTree) { - // render the AST into source - sourceTree.acceptVisitor(new InsertParenthesesVisitor(), null); - sourceTree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); - } - - public String writeSourceToString(CompilationUnit sourceTree) { - StringWriter writer = new StringWriter(); - writeSource(writer, sourceTree); - return writer.toString(); - } - - private static void runCustomTransforms(AstBuilder builder, DecompilerContext context) { - List transformers = Arrays.asList( - new ObfuscatedEnumSwitchRewriterTransform(context), - new VarargsFixer(context), - new RemoveObjectCasts(context), - new Java8Generics(), - new InvalidIdentifierFix() - ); - for (IAstTransform transform : transformers) { - transform.run(builder.getCompilationUnit()); - } - } -} diff --git a/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java deleted file mode 100644 index f6eee69..0000000 --- a/src/main/java/cuchaz/enigma/SynchronizedTypeLoader.java +++ /dev/null @@ -1,20 +0,0 @@ -package cuchaz.enigma; - -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; - -/** - * Typeloader with synchronized tryLoadType method - */ -public class SynchronizedTypeLoader implements ITypeLoader { - private final ITypeLoader delegate; - - public SynchronizedTypeLoader(ITypeLoader delegate) { - this.delegate = delegate; - } - - @Override - public synchronized boolean tryLoadType(String internalName, Buffer buffer) { - return delegate.tryLoadType(internalName, buffer); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/AddJavadocsAstTransform.java b/src/main/java/cuchaz/enigma/analysis/AddJavadocsAstTransform.java deleted file mode 100644 index 17ae63d..0000000 --- a/src/main/java/cuchaz/enigma/analysis/AddJavadocsAstTransform.java +++ /dev/null @@ -1,135 +0,0 @@ -package cuchaz.enigma.analysis; - -import com.google.common.base.Function; -import com.google.common.base.Strings; -import com.strobel.assembler.metadata.ParameterDefinition; -import com.strobel.decompiler.languages.java.ast.*; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.stream.Stream; - -public final class AddJavadocsAstTransform implements IAstTransform { - - private final EntryRemapper remapper; - - public AddJavadocsAstTransform(EntryRemapper remapper) { - this.remapper = remapper; - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(remapper), null); - } - - static class Visitor extends DepthFirstAstVisitor { - - private final EntryRemapper remapper; - - Visitor(EntryRemapper remapper) { - this.remapper = remapper; - } - - private void addDoc(T node, Function> retriever) { - final Comment[] comments = getComments(node, retriever); - if (comments != null) { - node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments); - } - } - - private Comment[] getComments(T node, Function> retriever) { - final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); - final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc()); - return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st, - CommentType.Documentation)).toArray(Comment[]::new); - } - - private Comment[] getParameterComments(ParameterDeclaration node, Function> retriever) { - final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); - final Comment[] ret = getComments(node, retriever); - if (ret != null) { - final String paramPrefix = "@param " + mapping.getTargetName() + " "; - final String indent = Strings.repeat(" ", paramPrefix.length()); - ret[0].setContent(paramPrefix + ret[0].getContent()); - for (int i = 1; i < ret.length; i++) { - ret[i].setContent(indent + ret[i].getContent()); - } - } - return ret; - } - - private void visitMethod(AstNode node) { - final MethodDefEntry methodDefEntry = MethodDefEntry.parse(node.getUserData(Keys.METHOD_DEFINITION)); - final Comment[] baseComments = getComments(node, $ -> methodDefEntry); - List comments = new ArrayList<>(); - if (baseComments != null) - Collections.addAll(comments, baseComments); - - for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) { - ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION); - final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(), - true, - TypeDescriptor.parse(def.getParameterType()), null)); - if (paramComments != null) - Collections.addAll(comments, paramComments); - } - - if (!comments.isEmpty()) { - if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) { - comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation)); - } - final AstNode oldFirst = node.getFirstChild(); - for (Comment comment : comments) { - node.insertChildBefore(oldFirst, comment, Roles.COMMENT); - } - } - } - - @Override - protected Void visitChildren(AstNode node, Void data) { - for (final AstNode child : node.getChildren()) { - child.acceptVisitor(this, data); - } - return null; - } - - @Override - public Void visitMethodDeclaration(MethodDeclaration node, Void data) { - visitMethod(node); - return super.visitMethodDeclaration(node, data); - } - - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) { - visitMethod(node); - return super.visitConstructorDeclaration(node, data); - } - - @Override - public Void visitFieldDeclaration(FieldDeclaration node, Void data) { - addDoc(node, dec -> FieldDefEntry.parse(dec.getUserData(Keys.FIELD_DEFINITION))); - return super.visitFieldDeclaration(node, data); - } - - @Override - public Void visitTypeDeclaration(TypeDeclaration node, Void data) { - addDoc(node, dec -> ClassDefEntry.parse(dec.getUserData(Keys.TYPE_DEFINITION))); - return super.visitTypeDeclaration(node, data); - } - - @Override - public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) { - addDoc(node, dec -> FieldDefEntry.parse(dec.getUserData(Keys.FIELD_DEFINITION))); - return super.visitEnumValueDeclaration(node, data); - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java index 12ef709..fddd9a8 100644 --- a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java +++ b/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java @@ -1,16 +1,17 @@ package cuchaz.enigma.analysis; -import com.strobel.core.Pair; import cuchaz.enigma.api.EnigmaPlugin; import cuchaz.enigma.api.EnigmaPluginContext; import cuchaz.enigma.api.service.JarIndexerService; import cuchaz.enigma.api.service.NameProposalService; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.Decompilers; +import cuchaz.enigma.source.procyon.ProcyonDecompiler; import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; +import cuchaz.enigma.utils.Pair; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; @@ -34,7 +35,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.function.UnaryOperator; public final class BuiltinPlugin implements EnigmaPlugin { @@ -44,6 +44,7 @@ public final class BuiltinPlugin implements EnigmaPlugin { @Override public void init(EnigmaPluginContext ctx) { registerEnumNamingService(ctx); + registerDecompilerServices(ctx); } private void registerEnumNamingService(EnigmaPluginContext ctx) { @@ -54,6 +55,11 @@ public final class BuiltinPlugin implements EnigmaPlugin { ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); } + private void registerDecompilerServices(EnigmaPluginContext ctx) { + ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON); + ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR); + } + private static final class EnumFieldNameFindingVisitor extends ClassVisitor { private ClassEntry clazz; diff --git a/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/src/main/java/cuchaz/enigma/analysis/ClassCache.java index 8453df1..d97b204 100644 --- a/src/main/java/cuchaz/enigma/analysis/ClassCache.java +++ b/src/main/java/cuchaz/enigma/analysis/ClassCache.java @@ -3,7 +3,7 @@ package cuchaz.enigma.analysis; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableSet; -import cuchaz.enigma.CompiledSource; +import cuchaz.enigma.ClassProvider; import cuchaz.enigma.ProgressListener; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; @@ -22,7 +22,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; -public final class ClassCache implements AutoCloseable, CompiledSource { +public final class ClassCache implements AutoCloseable, ClassProvider { private final FileSystem fileSystem; private final ImmutableSet classNames; diff --git a/src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java deleted file mode 100644 index 991e91d..0000000 --- a/src/main/java/cuchaz/enigma/analysis/DropImportAstTransform.java +++ /dev/null @@ -1,33 +0,0 @@ -package cuchaz.enigma.analysis; - -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; -import com.strobel.decompiler.languages.java.ast.ImportDeclaration; -import com.strobel.decompiler.languages.java.ast.PackageDeclaration; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -public final class DropImportAstTransform implements IAstTransform { - public static final DropImportAstTransform INSTANCE = new DropImportAstTransform(); - - private DropImportAstTransform() { - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(), null); - } - - static class Visitor extends DepthFirstAstVisitor { - @Override - public Void visitPackageDeclaration(PackageDeclaration node, Void data) { - node.remove(); - return null; - } - - @Override - public Void visitImportDeclaration(ImportDeclaration node, Void data) { - node.remove(); - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/DropVarModifiersAstTransform.java b/src/main/java/cuchaz/enigma/analysis/DropVarModifiersAstTransform.java deleted file mode 100644 index 0be5891..0000000 --- a/src/main/java/cuchaz/enigma/analysis/DropVarModifiersAstTransform.java +++ /dev/null @@ -1,37 +0,0 @@ -package cuchaz.enigma.analysis; - -import com.strobel.decompiler.languages.java.ast.*; -import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; - -import javax.lang.model.element.Modifier; - -public final class DropVarModifiersAstTransform implements IAstTransform { - public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform(); - - private DropVarModifiersAstTransform() { - } - - @Override - public void run(AstNode compilationUnit) { - compilationUnit.acceptVisitor(new Visitor(), null); - } - - static class Visitor extends DepthFirstAstVisitor { - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, Void data) { - for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) { - if (modifierToken.getModifier() == Modifier.FINAL) { - modifierToken.remove(); - } - } - - return null; - } - - @Override - public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { - node.removeModifier(Modifier.FINAL); - return null; - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java b/src/main/java/cuchaz/enigma/analysis/SourceIndex.java deleted file mode 100644 index a800f43..0000000 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndex.java +++ /dev/null @@ -1,240 +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.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.strobel.decompiler.languages.Region; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; -import com.strobel.decompiler.languages.java.ast.Identifier; -import com.strobel.decompiler.languages.java.ast.TypeDeclaration; -import cuchaz.enigma.gui.SourceRemapper; -import cuchaz.enigma.translation.mapping.EntryResolver; -import cuchaz.enigma.translation.mapping.ResolutionStrategy; -import cuchaz.enigma.translation.representation.entry.Entry; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class SourceIndex { - private static Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); - - private String source; - private TreeMap, Entry>> tokenToReference; - private Multimap, Entry>, Token> referenceToTokens; - private Map, Token> declarationToToken; - private List lineOffsets; - private boolean ignoreBadTokens; - - public SourceIndex(String source) { - this(source, true); - } - - public SourceIndex(String source, boolean ignoreBadTokens) { - this.source = source; - this.ignoreBadTokens = ignoreBadTokens; - this.tokenToReference = new TreeMap<>(); - this.referenceToTokens = HashMultimap.create(); - this.declarationToToken = Maps.newHashMap(); - calculateLineOffsets(); - } - - public static SourceIndex buildIndex(String sourceString, CompilationUnit sourceTree, boolean ignoreBadTokens) { - SourceIndex index = new SourceIndex(sourceString, ignoreBadTokens); - sourceTree.acceptVisitor(new SourceIndexVisitor(), index); - - return index; - } - - private void calculateLineOffsets() { - // count the lines - this.lineOffsets = Lists.newArrayList(); - this.lineOffsets.add(0); - for (int i = 0; i < source.length(); i++) { - if (source.charAt(i) == '\n') { - this.lineOffsets.add(i + 1); - } - } - } - - public SourceIndex remapTo(SourceRemapper.Result result) { - SourceIndex remapped = new SourceIndex(result.getSource(), ignoreBadTokens); - - for (Map.Entry, Token> entry : declarationToToken.entrySet()) { - remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); - } - - for (Map.Entry, Entry>, Collection> entry : referenceToTokens.asMap().entrySet()) { - EntryReference, Entry> reference = entry.getKey(); - Collection oldTokens = entry.getValue(); - - Collection newTokens = oldTokens.stream() - .map(result::getRemappedToken) - .collect(Collectors.toList()); - - remapped.referenceToTokens.putAll(reference, newTokens); - } - - for (Map.Entry, Entry>> entry : tokenToReference.entrySet()) { - remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue()); - } - - return remapped; - } - - public String getSource() { - return this.source; - } - - public Token getToken(AstNode node) { - - // get the text of the node - String name = ""; - if (node instanceof Identifier) { - name = ((Identifier) node).getName(); - } - - // get a token for this node's region - Region region = node.getRegion(); - if (region.getBeginLine() == 0 || region.getEndLine() == 0) { - // DEBUG - System.err.println(String.format("WARNING: %s \"%s\" has invalid region: %s", node.getNodeType(), name, region)); - return null; - } - Token token = new Token(toPos(region.getBeginLine(), region.getBeginColumn()), toPos(region.getEndLine(), region.getEndColumn()), this.source); - if (token.start == 0) { - // DEBUG - System.err.println(String.format("WARNING: %s \"%s\" has invalid start: %s", node.getNodeType(), name, region)); - return null; - } - - if (node instanceof Identifier && name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && name.lastIndexOf('$') >= 0 && !ANONYMOUS_INNER.matcher(name).matches()) { - TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; - if (type != null) { - name = type.getName(); - token.end = token.start + name.length(); - } - } - - // DEBUG - // System.out.println( String.format( "%s \"%s\" region: %s", node.getNodeType(), name, region ) ); - - // Tokens can have $ in name, even for top-level classes - //if (name.lastIndexOf('$') >= 0 && this.ignoreBadTokens) { - // // DEBUG - // System.err.println(String.format("WARNING: %s \"%s\" is probably a bad token. It was ignored", node.getNodeType(), name)); - // return null; - //} - - return token; - } - - public void addReference(AstNode node, Entry deobfEntry, Entry deobfContext) { - Token token = getToken(node); - if (token != null) { - EntryReference, Entry> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); - this.tokenToReference.put(token, deobfReference); - this.referenceToTokens.put(deobfReference, token); - } - } - - public void addDeclaration(AstNode node, Entry deobfEntry) { - Token token = getToken(node); - if (token != null) { - EntryReference, Entry> reference = new EntryReference<>(deobfEntry, token.text); - this.tokenToReference.put(token, reference); - this.referenceToTokens.put(reference, token); - this.declarationToToken.put(deobfEntry, token); - } - } - - public Token getReferenceToken(int pos) { - Token token = this.tokenToReference.floorKey(new Token(pos, pos, null)); - if (token != null && token.contains(pos)) { - return token; - } - return null; - } - - public Collection getReferenceTokens(EntryReference, Entry> deobfReference) { - return this.referenceToTokens.get(deobfReference); - } - - @Nullable - public EntryReference, Entry> getReference(Token token) { - if (token == null) { - return null; - } - return this.tokenToReference.get(token); - } - - public Iterable referenceTokens() { - return this.tokenToReference.keySet(); - } - - public Iterable declarationTokens() { - return this.declarationToToken.values(); - } - - public Iterable> declarations() { - return this.declarationToToken.keySet(); - } - - public Token getDeclarationToken(Entry entry) { - return this.declarationToToken.get(entry); - } - - public int getLineNumber(int pos) { - // line number is 1-based - int line = 0; - for (Integer offset : this.lineOffsets) { - if (offset > pos) { - break; - } - line++; - } - return line; - } - - public int getColumnNumber(int pos) { - // column number is 1-based - return pos - this.lineOffsets.get(getLineNumber(pos) - 1) + 1; - } - - private int toPos(int line, int col) { - // line and col are 1-based - return this.lineOffsets.get(line - 1) + col - 1; - } - - public void resolveReferences(EntryResolver resolver) { - // resolve all the classes in the source references - for (Token token : Lists.newArrayList(referenceToTokens.values())) { - EntryReference, Entry> reference = tokenToReference.get(token); - EntryReference, Entry> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); - - // replace the reference - tokenToReference.replace(token, resolvedReference); - - Collection tokens = referenceToTokens.removeAll(reference); - referenceToTokens.putAll(resolvedReference, tokens); - } - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java deleted file mode 100644 index 2a72cb1..0000000 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexClassVisitor.java +++ /dev/null @@ -1,96 +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.strobel.assembler.metadata.FieldDefinition; -import com.strobel.assembler.metadata.MethodDefinition; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; -import com.strobel.decompiler.languages.TextLocation; -import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; - -public class SourceIndexClassVisitor extends SourceIndexVisitor { - private ClassDefEntry classEntry; - - public SourceIndexClassVisitor(ClassDefEntry classEntry) { - this.classEntry = classEntry; - } - - @Override - public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { - // is this this class, or a subtype? - TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassDefEntry classEntry = ClassDefEntry.parse(def); - if (!classEntry.equals(this.classEntry)) { - // it's a subtype, recurse - index.addDeclaration(node.getNameToken(), classEntry); - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); - } - - return visitChildren(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.classEntry); - } - - return visitChildren(node, index); - } - - @Override - public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { - MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - MethodDefEntry methodEntry = MethodDefEntry.parse(def); - AstNode tokenNode = node.getNameToken(); - if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { - // for static initializers, check elsewhere for the token node - tokenNode = node.getModifiers().firstOrNullObject(); - } - index.addDeclaration(tokenNode, methodEntry); - return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); - } - - @Override - public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { - MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); - MethodDefEntry methodEntry = MethodDefEntry.parse(def); - index.addDeclaration(node.getNameToken(), methodEntry); - return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); - } - - @Override - public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { - FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldDefEntry fieldEntry = FieldDefEntry.parse(def); - assert (node.getVariables().size() == 1); - VariableInitializer variable = node.getVariables().firstOrNullObject(); - index.addDeclaration(variable.getNameToken(), fieldEntry); - return visitChildren(node, index); - } - - @Override - public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { - // treat enum declarations as field declarations - FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); - FieldDefEntry fieldEntry = FieldDefEntry.parse(def); - index.addDeclaration(node.getNameToken(), fieldEntry); - return visitChildren(node, index); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java deleted file mode 100644 index dfe58ba..0000000 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexMethodVisitor.java +++ /dev/null @@ -1,216 +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.*; -import com.strobel.decompiler.ast.Variable; -import com.strobel.decompiler.languages.TextLocation; -import com.strobel.decompiler.languages.java.ast.*; -import cuchaz.enigma.translation.representation.MethodDescriptor; -import cuchaz.enigma.translation.representation.TypeDescriptor; -import cuchaz.enigma.translation.representation.entry.*; - -import java.lang.Error; -import java.util.HashMap; -import java.util.Map; - -public class SourceIndexMethodVisitor extends SourceIndexVisitor { - private final MethodDefEntry methodEntry; - - private Multimap unmatchedIdentifier = HashMultimap.create(); - private Map> identifierEntryCache = new HashMap<>(); - - public SourceIndexMethodVisitor(MethodDefEntry methodEntry) { - this.methodEntry = methodEntry; - } - - @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()); - MethodEntry methodEntry = null; - if (ref instanceof MethodReference) { - methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(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 visitChildren(node, index); - } - - @Override - public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - if (ref instanceof FieldReference) { - // 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 = new ClassEntry(ref.getDeclaringType().getInternalName()); - FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); - index.addReference(node.getMemberNameToken(), fieldEntry, this.methodEntry); - } - - return visitChildren(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.methodEntry); - } - - return visitChildren(node, index); - } - - @Override - public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { - ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); - int parameterIndex = def.getSlot(); - - if (parameterIndex >= 0) { - MethodDefEntry ownerMethod = methodEntry; - if (def.getMethod() instanceof MethodDefinition) { - ownerMethod = MethodDefEntry.parse((MethodDefinition) def.getMethod()); - } - - TypeDescriptor parameterType = TypeDescriptor.parse(def.getParameterType()); - LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null); - Identifier identifier = node.getNameToken(); - // cache the argument entry and the identifier - identifierEntryCache.put(identifier.getName(), localVariableEntry); - index.addDeclaration(identifier, localVariableEntry); - } - - return visitChildren(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 TypeDescriptor(ref.getErasedSignature())); - index.addReference(node.getIdentifierToken(), fieldEntry, this.methodEntry); - } else - this.checkIdentifier(node, index); - return visitChildren(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 && node.getType() instanceof SimpleType) { - SimpleType simpleTypeNode = (SimpleType) node.getType(); - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - MethodEntry constructorEntry = new MethodEntry(classEntry, "", new MethodDescriptor(ref.getErasedSignature())); - index.addReference(simpleTypeNode.getIdentifierToken(), constructorEntry, this.methodEntry); - } - - return visitChildren(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(); - Variable variable = initializer.getUserData(Keys.VARIABLE); - if (variable != null) { - VariableDefinition originalVariable = variable.getOriginalVariable(); - if (originalVariable != null) { - int variableIndex = originalVariable.getSlot(); - if (variableIndex >= 0) { - MethodDefEntry ownerMethod = MethodDefEntry.parse(originalVariable.getDeclaringMethod()); - TypeDescriptor variableType = TypeDescriptor.parse(originalVariable.getVariableType()); - LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null); - identifierEntryCache.put(identifier.getName(), localVariableEntry); - addDeclarationToUnmatched(identifier.getName(), index); - index.addDeclaration(identifier, localVariableEntry); - } - } - } - } - } - return visitChildren(node, index); - } - - @Override - public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { - MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); - - if (ref instanceof MethodReference) { - // get the behavior entry - ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); - MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); - - // get the node for the token - AstNode methodNameToken = node.getMethodNameToken(); - AstNode targetToken = node.getTarget(); - - if (methodNameToken != null) { - index.addReference(methodNameToken, methodEntry, this.methodEntry); - } - - if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) { - index.addReference(targetToken, methodEntry.getParent(), this.methodEntry); - } - } - - return visitChildren(node, index); - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.java deleted file mode 100644 index 8bd00a8..0000000 --- a/src/main/java/cuchaz/enigma/analysis/SourceIndexVisitor.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 com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.decompiler.languages.java.ast.AstNode; -import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; -import com.strobel.decompiler.languages.java.ast.Keys; -import com.strobel.decompiler.languages.java.ast.TypeDeclaration; -import cuchaz.enigma.translation.representation.entry.ClassDefEntry; - -public class SourceIndexVisitor extends DepthFirstAstVisitor { - @Override - public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { - TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); - ClassDefEntry classEntry = ClassDefEntry.parse(def); - index.addDeclaration(node.getNameToken(), classEntry); - - return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); - } - - @Override - protected Void visitChildren(AstNode node, SourceIndex index) { - for (final AstNode child : node.getChildren()) { - child.acceptVisitor(this, index); - } - return null; - } -} diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java index 12e0aa6..f0155e5 100644 --- a/src/main/java/cuchaz/enigma/analysis/Token.java +++ b/src/main/java/cuchaz/enigma/analysis/Token.java @@ -17,12 +17,10 @@ public class Token implements Comparable { public int end; public String text; - public Token(int start, int end, String source) { + public Token(int start, int end, String text) { this.start = start; this.end = end; - if (source != null) { - this.text = source.substring(start, end); - } + this.text = text; } public int getRenameOffset(String to) { diff --git a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java b/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.java deleted file mode 100644 index c85d97a..0000000 --- a/src/main/java/cuchaz/enigma/analysis/TreeDumpVisitor.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.strobel.componentmodel.Key; -import com.strobel.decompiler.languages.java.ast.*; -import com.strobel.decompiler.patterns.Pattern; - -import java.io.*; -import java.nio.charset.Charset; - -public class TreeDumpVisitor extends DepthFirstAstVisitor { - - private File file; - private Writer out; - - public TreeDumpVisitor(File file) { - this.file = file; - } - - @Override - public Void visitCompilationUnit(CompilationUnit node, Void ignored) { - try { - out = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8")); - visitChildren(node, ignored); - out.close(); - return null; - } catch (IOException ex) { - throw new Error(ex); - } - } - - @Override - protected Void visitChildren(AstNode node, Void ignored) { - // show the tree - try { - out.write(getIndent(node) + node.getClass().getSimpleName() + " " + getText(node) + " " + dumpUserData(node) + " " + node.getRegion() + "\n"); - } catch (IOException ex) { - throw new Error(ex); - } - - // recurse - for (final AstNode child : node.getChildren()) { - child.acceptVisitor(this, ignored); - } - return null; - } - - private String getText(AstNode node) { - if (node instanceof Identifier) { - return "\"" + ((Identifier) node).getName() + "\""; - } - return ""; - } - - private String dumpUserData(AstNode node) { - StringBuilder buf = new StringBuilder(); - for (Key key : Keys.ALL_KEYS) { - Object val = node.getUserData(key); - if (val != null) { - buf.append(String.format(" [%s=%s]", key, val)); - } - } - return buf.toString(); - } - - private String getIndent(AstNode node) { - StringBuilder buf = new StringBuilder(); - int depth = getDepth(node); - for (int i = 0; i < depth; i++) { - buf.append("\t"); - } - return buf.toString(); - } - - private int getDepth(AstNode node) { - int depth = -1; - while (node != null) { - depth++; - node = node.getParent(); - } - return depth; - } -} diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java index bc23d01..3d15dac 100644 --- a/src/main/java/cuchaz/enigma/command/DecompileCommand.java +++ b/src/main/java/cuchaz/enigma/command/DecompileCommand.java @@ -2,8 +2,12 @@ package cuchaz.enigma.command; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.Decompilers; +import java.lang.reflect.Field; import java.nio.file.Path; +import java.util.Locale; public class DecompileCommand extends Command { @@ -13,7 +17,7 @@ public class DecompileCommand extends Command { @Override public String getUsage() { - return " []"; + return " []"; } @Override @@ -23,16 +27,27 @@ public class DecompileCommand extends Command { @Override public void run(String... args) throws Exception { - Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath(); - Path fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)).toPath(); - Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); + String decompilerName = getArg(args, 1, "decompiler", true); + Path fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)).toPath(); + Path fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)).toPath(); + Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); + + DecompilerService decompilerService; + + try { + Field decompilerField = Decompilers.class.getField(decompilerName.toUpperCase(Locale.ROOT)); + decompilerService = (DecompilerService) decompilerField.get(null); + } catch (NoSuchFieldException e) { + System.err.println("Decompiler not found."); + return; + } EnigmaProject project = openProject(fileJarIn, fileMappings); ProgressListener progress = new ConsoleProgressListener(); EnigmaProject.JarExport jar = project.exportRemappedJar(progress); - EnigmaProject.SourceExport source = jar.decompile(progress); + EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); source.write(fileJarOut, progress); } diff --git a/src/main/java/cuchaz/enigma/config/Config.java b/src/main/java/cuchaz/enigma/config/Config.java index a00fe2d..15a974c 100644 --- a/src/main/java/cuchaz/enigma/config/Config.java +++ b/src/main/java/cuchaz/enigma/config/Config.java @@ -3,6 +3,8 @@ package cuchaz.enigma.config; import com.bulenkov.darcula.DarculaLaf; import com.google.common.io.Files; import com.google.gson.*; +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.Decompilers; import cuchaz.enigma.utils.I18n; @@ -137,6 +139,19 @@ public class Config { } } + public enum Decompiler { + PROCYON("Procyon", Decompilers.PROCYON), + CFR("CFR", Decompilers.CFR); + + public final DecompilerService service; + public final String name; + + Decompiler(String name, DecompilerService service) { + this.name = name; + this.service = service; + } + } + private static final File DIR_HOME = new File(System.getProperty("user.home")); private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma"); private static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json"); @@ -167,11 +182,13 @@ public class Config { public Integer lineNumbersBackground; public Integer lineNumbersSelected; public Integer lineNumbersForeground; - + public String language = I18n.DEFAULT_LANGUAGE; public LookAndFeel lookAndFeel = LookAndFeel.DEFAULT; + public Decompiler decompiler = Decompiler.PROCYON; + private Config() { gson = new GsonBuilder() .registerTypeAdapter(Integer.class, new IntSerializer()) @@ -217,6 +234,7 @@ public class Config { public void reset() throws IOException { this.lookAndFeel = LookAndFeel.DEFAULT; this.lookAndFeel.apply(this); + this.decompiler = Decompiler.PROCYON; this.language = I18n.DEFAULT_LANGUAGE; this.saveConfig(); } diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java index c1b163d..4d6b557 100644 --- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java +++ b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java @@ -3,14 +3,14 @@ package cuchaz.enigma.gui; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.EnigmaServices; import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.analysis.SourceIndex; import cuchaz.enigma.analysis.Token; import cuchaz.enigma.api.service.NameProposalService; import cuchaz.enigma.gui.highlight.TokenHighlightType; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.SourceIndex; import cuchaz.enigma.translation.LocalNameGenerator; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryRemapper; -import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.ResolutionStrategy; import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.ClassEntry; @@ -19,7 +19,6 @@ import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry; import javax.annotation.Nullable; import java.util.*; -import java.util.stream.Stream; public class DecompiledClassSource { private final ClassEntry classEntry; diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java index dc5010c..25a1057 100644 --- a/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -13,8 +13,9 @@ package cuchaz.enigma.gui; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import com.strobel.decompiler.languages.java.ast.CompilationUnit; -import cuchaz.enigma.*; +import cuchaz.enigma.Enigma; +import cuchaz.enigma.EnigmaProfile; +import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.analysis.*; import cuchaz.enigma.api.service.ObfuscationTestService; import cuchaz.enigma.bytecode.translators.SourceFixVisitor; @@ -23,6 +24,7 @@ import cuchaz.enigma.gui.dialog.ProgressDialog; import cuchaz.enigma.gui.stats.StatsGenerator; import cuchaz.enigma.gui.stats.StatsMember; import cuchaz.enigma.gui.util.History; +import cuchaz.enigma.source.*; import cuchaz.enigma.throwables.MappingParseException; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.*; @@ -36,16 +38,16 @@ import cuchaz.enigma.utils.I18n; import cuchaz.enigma.utils.ReadableToken; import cuchaz.enigma.utils.Utils; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; import javax.annotation.Nullable; -import javax.swing.*; -import java.awt.*; +import javax.swing.JOptionPane; +import java.awt.Desktop; import java.awt.event.ItemEvent; import java.io.*; import java.nio.file.Path; import java.util.Collection; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; @@ -65,19 +67,23 @@ public class GuiController { public final Enigma enigma; public EnigmaProject project; - private SourceProvider sourceProvider; + private DecompilerService decompilerService; + private Decompiler decompiler; private IndexTreeBuilder indexTreeBuilder; private Path loadedMappingPath; private MappingFormat loadedMappingFormat; private DecompiledClassSource currentSource; + private Source uncommentedSource; public GuiController(Gui gui, EnigmaProfile profile) { this.gui = gui; this.enigma = Enigma.builder() .setProfile(profile) .build(); + + decompilerService = Config.getInstance().decompiler.service; } public boolean isDirty() { @@ -89,19 +95,27 @@ public class GuiController { return ProgressDialog.runOffThread(gui.getFrame(), progress -> { project = enigma.openJar(jarPath, progress); - indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); - - CompiledSourceTypeLoader typeLoader = new CompiledSourceTypeLoader(project.getClassCache()); - typeLoader.addVisitor(visitor -> new SourceFixVisitor(Opcodes.ASM5, visitor, project.getJarIndex())); - sourceProvider = new SourceProvider(SourceProvider.createSettings(), typeLoader); - + decompiler = createDecompiler(); gui.onFinishOpenJar(jarPath.getFileName().toString()); - refreshClasses(); }); } + private Decompiler createDecompiler() { + return decompilerService.create(name -> { + ClassNode node = project.getClassCache().getClassNode(name); + + if (node == null) { + return null; + } + + ClassNode fixedNode = new ClassNode(); + node.accept(new SourceFixVisitor(Opcodes.ASM7, fixedNode, project.getJarIndex())); + return fixedNode; + }, new SourceSettings(true, true)); + } + public void closeJar() { this.project = null; this.gui.onCloseJar(); @@ -176,7 +190,7 @@ public class GuiController { return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> { EnigmaProject.JarExport jar = project.exportRemappedJar(progress); - EnigmaProject.SourceExport source = jar.decompile(progress); + EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService); source.write(path, progress); }); @@ -210,6 +224,7 @@ public class GuiController { if (this.currentSource == null) { return null; } + SourceIndex index = this.currentSource.getIndex(); return new ReadableToken( index.getLineNumber(token.start), @@ -369,27 +384,27 @@ public class GuiController { } private void refreshCurrentClass(EntryReference, Entry> reference) { - refreshCurrentClass(reference, false); + refreshCurrentClass(reference, RefreshMode.MINIMAL); } - private void refreshCurrentClass(EntryReference, Entry> reference, boolean forceDecomp) { + private void refreshCurrentClass(EntryReference, Entry> reference, RefreshMode mode) { if (currentSource != null) { loadClass(currentSource.getEntry(), () -> { if (reference != null) { showReference(reference); } - }, forceDecomp); + }, mode); } } private void loadClass(ClassEntry classEntry, Runnable callback) { - loadClass(classEntry, callback, false); + loadClass(classEntry, callback, RefreshMode.MINIMAL); } - private void loadClass(ClassEntry classEntry, Runnable callback, boolean forceDecomp) { + private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) { ClassEntry targetClass = classEntry.getOutermostClass(); - boolean requiresDecompile = forceDecomp || currentSource == null || !currentSource.getEntry().equals(targetClass); + boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass); if (requiresDecompile) { currentSource = null; // Or the GUI may try to find a nonexistent token gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling")); @@ -397,8 +412,8 @@ public class GuiController { DECOMPILER_SERVICE.submit(() -> { try { - if (requiresDecompile) { - currentSource = decompileSource(targetClass); + if (requiresDecompile || mode == RefreshMode.JAVADOCS) { + currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS); } remapSource(project.getMapper().getDeobfuscator()); @@ -410,21 +425,20 @@ public class GuiController { }); } - private DecompiledClassSource decompileSource(ClassEntry targetClass) { + private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) { try { - CompilationUnit sourceTree = (CompilationUnit) sourceProvider.getSources(targetClass.getFullName()).clone(); - if (sourceTree == null) { - gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass); - return DecompiledClassSource.text(targetClass, "Unable to find class"); + if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) { + uncommentedSource = decompiler.getSource(targetClass.getFullName()); } - DropImportAstTransform.INSTANCE.run(sourceTree); - DropVarModifiersAstTransform.INSTANCE.run(sourceTree); - new AddJavadocsAstTransform(project.getMapper()).run(sourceTree); + Source source = uncommentedSource.addJavadocs(project.getMapper()); - String sourceString = sourceProvider.writeSourceToString(sourceTree); + if (source == null) { + gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass); + return DecompiledClassSource.text(targetClass, "Unable to find class"); + } - SourceIndex index = SourceIndex.buildIndex(sourceString, sourceTree, true); + SourceIndex index = source.index(); index.resolveReferences(project.getMapper().getObfResolver()); return new DecompiledClassSource(targetClass, index); @@ -535,7 +549,7 @@ public class GuiController { public void changeDocs(EntryReference, Entry> reference, String updatedDocs) { changeDoc(reference.getNameableEntry(), updatedDocs); - refreshCurrentClass(reference, true); + refreshCurrentClass(reference, RefreshMode.JAVADOCS); } public void changeDoc(Entry obfEntry, String newDoc) { @@ -582,4 +596,11 @@ public class GuiController { } }); } + + public void setDecompiler(DecompilerService service) { + uncommentedSource = null; + decompilerService = service; + decompiler = createDecompiler(); + refreshCurrentClass(null, RefreshMode.FULL); + } } diff --git a/src/main/java/cuchaz/enigma/gui/RefreshMode.java b/src/main/java/cuchaz/enigma/gui/RefreshMode.java new file mode 100644 index 0000000..87cb83b --- /dev/null +++ b/src/main/java/cuchaz/enigma/gui/RefreshMode.java @@ -0,0 +1,7 @@ +package cuchaz.enigma.gui; + +public enum RefreshMode { + MINIMAL, + JAVADOCS, + FULL +} diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java index 50f0849..185e83c 100644 --- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java +++ b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java @@ -8,6 +8,7 @@ import cuchaz.enigma.gui.dialog.SearchDialog; import cuchaz.enigma.gui.stats.StatsMember; import cuchaz.enigma.translation.mapping.serde.MappingFormat; import cuchaz.enigma.utils.I18n; +import cuchaz.enigma.utils.Utils; import javax.swing.*; import java.awt.*; @@ -200,6 +201,27 @@ public class MenuBar extends JMenuBar { item.addActionListener(event -> this.gui.close()); } } + + { + JMenu menu = new JMenu(I18n.translate("menu.decompiler")); + add(menu); + + for (Config.Decompiler decompiler : Config.Decompiler.values()) { + JMenuItem label = new JMenuItem(decompiler.name); + menu.add(label); + label.addActionListener(event -> { + gui.getController().setDecompiler(decompiler.service); + + try { + Config.getInstance().decompiler = decompiler; + Config.getInstance().saveConfig(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } + { JMenu menu = new JMenu(I18n.translate("menu.view")); this.add(menu); diff --git a/src/main/java/cuchaz/enigma/source/Decompiler.java b/src/main/java/cuchaz/enigma/source/Decompiler.java new file mode 100644 index 0000000..c9666d5 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Decompiler.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.source; + +public interface Decompiler { + Source getSource(String className); +} diff --git a/src/main/java/cuchaz/enigma/source/DecompilerService.java b/src/main/java/cuchaz/enigma/source/DecompilerService.java new file mode 100644 index 0000000..377ccbc --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/DecompilerService.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceType; + +public interface DecompilerService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("decompiler"); + + Decompiler create(ClassProvider classProvider, SourceSettings settings); +} diff --git a/src/main/java/cuchaz/enigma/source/Decompilers.java b/src/main/java/cuchaz/enigma/source/Decompilers.java new file mode 100644 index 0000000..7d154a6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Decompilers.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.source.cfr.CfrDecompiler; +import cuchaz.enigma.source.procyon.ProcyonDecompiler; + +public class Decompilers { + public static final DecompilerService PROCYON = ProcyonDecompiler::new; + public static final DecompilerService CFR = CfrDecompiler::new; +} diff --git a/src/main/java/cuchaz/enigma/source/Source.java b/src/main/java/cuchaz/enigma/source/Source.java new file mode 100644 index 0000000..43c4de0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Source.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.translation.mapping.EntryRemapper; + +public interface Source { + String asString(); + + Source addJavadocs(EntryRemapper remapper); + + SourceIndex index(); +} diff --git a/src/main/java/cuchaz/enigma/source/SourceIndex.java b/src/main/java/cuchaz/enigma/source/SourceIndex.java new file mode 100644 index 0000000..6a335ec --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/SourceIndex.java @@ -0,0 +1,174 @@ +package cuchaz.enigma.source; + +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.analysis.EntryReference; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.gui.SourceRemapper; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public class SourceIndex { + private String source; + private List lineOffsets; + private final TreeMap, Entry>> tokenToReference; + private final Multimap, Entry>, Token> referenceToTokens; + private final Map, Token> declarationToToken; + + public SourceIndex() { + tokenToReference = new TreeMap<>(); + referenceToTokens = HashMultimap.create(); + declarationToToken = Maps.newHashMap(); + } + + public SourceIndex(String source) { + this(); + setSource(source); + } + + public void setSource(String source) { + this.source = source; + lineOffsets = Lists.newArrayList(); + lineOffsets.add(0); + + for (int i = 0; i < this.source.length(); i++) { + if (this.source.charAt(i) == '\n') { + lineOffsets.add(i + 1); + } + } + } + + public String getSource() { + return source; + } + + public int getLineNumber(int position) { + int line = 0; + + for (int offset : lineOffsets) { + if (offset > position) { + break; + } + + line++; + } + + return line; + } + + public int getColumnNumber(int position) { + return position - lineOffsets.get(getLineNumber(position) - 1) + 1; + } + + public int getPosition(int line, int column) { + return lineOffsets.get(line - 1) + column - 1; + } + + public Iterable> declarations() { + return declarationToToken.keySet(); + } + + public Iterable declarationTokens() { + return declarationToToken.values(); + } + + public Token getDeclarationToken(Entry entry) { + return declarationToToken.get(entry); + } + + public void addDeclaration(Token token, Entry deobfEntry) { + if (token != null) { + EntryReference, Entry> reference = new EntryReference<>(deobfEntry, token.text); + tokenToReference.put(token, reference); + referenceToTokens.put(reference, token); + declarationToToken.put(deobfEntry, token); + } + } + + public Iterable, Entry>> references() { + return referenceToTokens.keySet(); + } + + public EntryReference, Entry> getReference(Token token) { + if (token == null) { + return null; + } + + return tokenToReference.get(token); + } + + public Iterable referenceTokens() { + return tokenToReference.keySet(); + } + + public Token getReferenceToken(int pos) { + Token token = tokenToReference.floorKey(new Token(pos, pos, null)); + + if (token != null && token.contains(pos)) { + return token; + } + + return null; + } + + public Collection getReferenceTokens(EntryReference, Entry> deobfReference) { + return referenceToTokens.get(deobfReference); + } + + public void addReference(Token token, Entry deobfEntry, Entry deobfContext) { + if (token != null) { + EntryReference, Entry> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); + tokenToReference.put(token, deobfReference); + referenceToTokens.put(deobfReference, token); + } + } + + public void resolveReferences(EntryResolver resolver) { + // resolve all the classes in the source references + for (Token token : Lists.newArrayList(referenceToTokens.values())) { + EntryReference, Entry> reference = tokenToReference.get(token); + EntryReference, Entry> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); + + // replace the reference + tokenToReference.replace(token, resolvedReference); + + Collection tokens = referenceToTokens.removeAll(reference); + referenceToTokens.putAll(resolvedReference, tokens); + } + } + + public SourceIndex remapTo(SourceRemapper.Result result) { + SourceIndex remapped = new SourceIndex(result.getSource()); + + for (Map.Entry, Token> entry : declarationToToken.entrySet()) { + remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); + } + + for (Map.Entry, Entry>, Collection> entry : referenceToTokens.asMap().entrySet()) { + EntryReference, Entry> reference = entry.getKey(); + Collection oldTokens = entry.getValue(); + + Collection newTokens = oldTokens + .stream() + .map(result::getRemappedToken) + .collect(Collectors.toList()); + + remapped.referenceToTokens.putAll(reference, newTokens); + } + + for (Map.Entry, Entry>> entry : tokenToReference.entrySet()) { + remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue()); + } + + return remapped; + } +} diff --git a/src/main/java/cuchaz/enigma/source/SourceSettings.java b/src/main/java/cuchaz/enigma/source/SourceSettings.java new file mode 100644 index 0000000..f6c68e9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/SourceSettings.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +public class SourceSettings { + public final boolean removeImports; + public final boolean removeVariableFinal; + + public SourceSettings(boolean removeImports, boolean removeVariableFinal) { + this.removeImports = removeImports; + this.removeVariableFinal = removeVariableFinal; + } +} diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java new file mode 100644 index 0000000..9e37f16 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java @@ -0,0 +1,108 @@ +package cuchaz.enigma.source.cfr; + +import com.google.common.io.ByteStreams; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceSettings; +import org.benf.cfr.reader.apiunreleased.ClassFileSource2; +import org.benf.cfr.reader.apiunreleased.JarContent; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.mapping.MappingFactory; +import org.benf.cfr.reader.mapping.ObfuscationMapping; +import org.benf.cfr.reader.relationship.MemberNameResolver; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.state.TypeUsageCollectingDumper; +import org.benf.cfr.reader.util.AnalysisType; +import org.benf.cfr.reader.util.CannotLoadClassException; +import org.benf.cfr.reader.util.collections.ListFactory; +import org.benf.cfr.reader.util.getopt.Options; +import org.benf.cfr.reader.util.getopt.OptionsImpl; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + + +public class CfrDecompiler implements Decompiler { + private final DCCommonState state; + + public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { + Map options = new HashMap<>(); + + state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() { + @Override + public JarContent addJarContent(String s, AnalysisType analysisType) { + return null; + } + + @Override + public void informAnalysisRelativePathDetail(String usePath, String classFilePath) { + + } + + @Override + public Collection addJar(String jarPath) { + return null; + } + + @Override + public String getPossiblyRenamedPath(String path) { + return path; + } + + @Override + public Pair getClassFileContent(String path) { + ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); + + if (node == null) { + try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) { + if (classResource != null) { + return new Pair<>(ByteStreams.toByteArray(classResource), path); + } + } catch (IOException ignored) {} + + return null; + } + + ClassWriter cw = new ClassWriter(0); + node.accept(cw); + return new Pair<>(cw.toByteArray(), path); + } + }); + } + + @Override + public Source getSource(String className) { + DCCommonState state = this.state; + Options options = state.getOptions(); + + ObfuscationMapping mapping = MappingFactory.get(options, state); + state = new DCCommonState(state, mapping); + ClassFile tree = state.getClassFileMaybePath(className); + + state.configureWith(tree); + + // To make sure we're analysing the cached version + try { + tree = state.getClassFile(tree.getClassType()); + } catch (CannotLoadClassException ignored) {} + + if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) { + tree.loadInnerClasses(state); + } + + if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) { + MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes())); + } + + TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree); + tree.analyseTop(state, typeUsageCollector); + return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation()); + } +} diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java new file mode 100644 index 0000000..d4f2da6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.cfr; + +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.state.TypeUsageInformation; + +public class CfrSource implements Source { + private final ClassFile tree; + private final SourceIndex index; + private final String string; + + public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) { + this.tree = tree; + + EnigmaDumper dumper = new EnigmaDumper(typeUsages); + tree.dump(state.getObfuscationMapping().wrap(dumper)); + index = dumper.getIndex(); + string = dumper.getString(); + } + + @Override + public String asString() { + return string; + } + + @Override + public Source addJavadocs(EntryRemapper remapper) { + return this; // TODO + } + + @Override + public SourceIndex index() { + return index; + } +} diff --git a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java new file mode 100644 index 0000000..b9cdbea --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java @@ -0,0 +1,427 @@ +package cuchaz.enigma.source.cfr; + +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; +import org.benf.cfr.reader.bytecode.analysis.types.*; +import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable; +import org.benf.cfr.reader.entities.Field; +import org.benf.cfr.reader.entities.Method; +import org.benf.cfr.reader.mapping.NullMapping; +import org.benf.cfr.reader.mapping.ObfuscationMapping; +import org.benf.cfr.reader.state.TypeUsageInformation; +import org.benf.cfr.reader.util.collections.SetFactory; +import org.benf.cfr.reader.util.output.DelegatingDumper; +import org.benf.cfr.reader.util.output.Dumpable; +import org.benf.cfr.reader.util.output.Dumper; +import org.benf.cfr.reader.util.output.TypeContext; + +import java.util.Set; +import java.util.stream.Collectors; + +public class EnigmaDumper implements Dumper { + private int outputCount = 0; + private int indent; + private boolean atStart = true; + private boolean pendingCR = false; + private final StringBuilder sb = new StringBuilder(); + private final TypeUsageInformation typeUsageInformation; + private final Set emitted = SetFactory.newSet(); + private final SourceIndex index = new SourceIndex(); + private int position; + + + public EnigmaDumper(TypeUsageInformation typeUsageInformation) { + this.typeUsageInformation = typeUsageInformation; + } + + private void append(String s) { + sb.append(s); + position += s.length(); + } + + private String getDesc(JavaTypeInstance type) { + type = type.getDeGenerifiedType(); + + if (type instanceof JavaRefTypeInstance) { + return "L" + type.getRawName().replace('.', '/') + ";"; + } + + if (type instanceof JavaArrayTypeInstance) { + return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection()); + } + + if (type instanceof RawJavaType) { + switch ((RawJavaType) type) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case CHAR: + return "C"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + case VOID: + return "V"; + default: + throw new AssertionError(); + } + } + + throw new AssertionError(); + } + + private MethodEntry getMethodEntry(MethodPrototype method) { + if (method.getClassType() == null) { + return null; + } + + MethodDescriptor desc = new MethodDescriptor( + method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()), + new TypeDescriptor(method.getName().equals("") || method.getName().equals("") ? "V" : getDesc(method.getReturnType())) + ); + + return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc); + } + + private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) { + int variableIndex = method.isInstanceMethod() ? 1 : 0; + for (int i = 0; i < parameterIndex; i++) { + variableIndex += method.getArgs().get(parameterIndex).getStackType().getComputationCategory(); + } + + return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null); + } + + private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) { + return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type))); + } + + private ClassEntry getClassEntry(JavaTypeInstance type) { + return new ClassEntry(type.getRawName().replace('.', '/')); + } + + @Override + public Dumper beginBlockComment(boolean inline) { + print("/*").newln(); + return this; + } + + @Override + public Dumper endBlockComment() { + print(" */").newln(); + return this; + } + + @Override + public Dumper label(String s, boolean inline) { + processPendingCR(); + append(s); + append(":"); + return this; + } + + @Override + public Dumper comment(String s) { + append("// "); + append(s); + append("\n"); + return this; + } + + @Override + public void enqueuePendingCarriageReturn() { + pendingCR = true; + } + + @Override + public Dumper removePendingCarriageReturn() { + pendingCR = false; + return this; + } + + private void processPendingCR() { + if (pendingCR) { + append("\n"); + atStart = true; + pendingCR = false; + } + } + + @Override + public Dumper identifier(String s, Object ref, boolean defines) { + return print(s); + } + + @Override + public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = getMethodEntry(method); + + if (entry != null) { + if (defines) { + index.addDeclaration(token, entry); + } else { + index.addReference(token, entry, null); + } + } + + return identifier(name, null, defines); + } + + @Override + public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = getParameterEntry(method, index, name); + + if (entry != null) { + if (defines) { + this.index.addDeclaration(token, entry); + } else { + this.index.addReference(token, entry, null); + } + } + + return identifier(name, null, defines); + } + + @Override + public Dumper variableName(String name, NamedVariable variable, boolean defines) { + return identifier(name, null, defines); + } + + @Override + public Dumper packageName(JavaRefTypeInstance t) { + String s = t.getPackageName(); + + if (!s.isEmpty()) { + keyword("package ").print(s).endCodeln().newln(); + } + + return this; + } + + @Override + public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance()); + + if (entry != null) { + if (defines) { + index.addDeclaration(token, entry); + } else { + index.addReference(token, entry, null); + } + } + + identifier(name, null, defines); + return this; + } + + @Override + public Dumper print(String s) { + processPendingCR(); + doIndent(); + append(s); + atStart = s.endsWith("\n"); + outputCount++; + return this; + } + + @Override + public Dumper print(char c) { + return print(String.valueOf(c)); + } + + @Override + public Dumper newln() { + append("\n"); + atStart = true; + outputCount++; + return this; + } + + @Override + public Dumper endCodeln() { + append(";\n"); + atStart = true; + outputCount++; + return this; + } + + @Override + public Dumper keyword(String s) { + print(s); + return this; + } + + @Override + public Dumper operator(String s) { + print(s); + return this; + } + + @Override + public Dumper separator(String s) { + print(s); + return this; + } + + @Override + public Dumper literal(String s, Object o) { + print(s); + return this; + } + + private void doIndent() { + if (!atStart) return; + String indents = " "; + + for (int x = 0; x < indent; ++x) { + append(indents); + } + + atStart = false; + } + + @Override + public void indent(int diff) { + indent += diff; + } + + @Override + public Dumper dump(Dumpable d) { + if (d == null) { + keyword("null"); + return this; + } + + d.dump(this); + return this; + } + + @Override + public TypeUsageInformation getTypeUsageInformation() { + return typeUsageInformation; + } + + @Override + public ObfuscationMapping getObfuscationMapping() { + return NullMapping.INSTANCE; + } + + @Override + public String toString() { + return sb.toString(); + } + + @Override + public void addSummaryError(Method method, String s) {} + + @Override + public void close() { + } + + @Override + public boolean canEmitClass(JavaTypeInstance type) { + return emitted.add(type); + } + + @Override + public int getOutputCount() { + return outputCount; + } + + @Override + public Dumper dump(JavaTypeInstance type) { + return dump(type, TypeContext.None, false); + } + + @Override + public Dumper dump(JavaTypeInstance type, boolean defines) { + return dump(type, TypeContext.None, false); + } + + @Override + public Dumper dump(JavaTypeInstance type, TypeContext context) { + return dump(type, context, false); + } + + private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) { + doIndent(); + if (type instanceof JavaRefTypeInstance) { + int start = position; + type.dumpInto(this, typeUsageInformation, TypeContext.None); + int end = position; + Token token = new Token(start, end, sb.toString().substring(start, end)); + + if (defines) { + index.addDeclaration(token, getClassEntry(type)); + } else { + index.addReference(token, getClassEntry(type), null); + } + + return this; + } + + type.dumpInto(this, typeUsageInformation, context); + return this; + } + + @Override + public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { + return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation); + } + + public SourceIndex getIndex() { + index.setSource(getString()); + return index; + } + + public String getString() { + return sb.toString(); + } + + public static class WithTypeUsageInformationDumper extends DelegatingDumper { + private final TypeUsageInformation typeUsageInformation; + + WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) { + super(delegate); + this.typeUsageInformation = typeUsageInformation; + } + + @Override + public TypeUsageInformation getTypeUsageInformation() { + return typeUsageInformation; + } + + @Override + public Dumper dump(JavaTypeInstance javaTypeInstance) { + return dump(javaTypeInstance, TypeContext.None); + } + + @Override + public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) { + javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext); + return this; + } + + @Override + public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { + return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation); + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java new file mode 100644 index 0000000..2fae61a --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; + +public class EntryParser { + public static FieldDefEntry parse(FieldDefinition definition) { + ClassEntry owner = parse(definition.getDeclaringType()); + TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature()); + Signature signature = Signature.createTypedSignature(definition.getSignature()); + AccessFlags access = new AccessFlags(definition.getModifiers()); + return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null); + } + + public static ClassDefEntry parse(TypeDefinition def) { + String name = def.getInternalName(); + Signature signature = Signature.createSignature(def.getSignature()); + AccessFlags access = new AccessFlags(def.getModifiers()); + ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null; + ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, signature, access, superClass, interfaces); + } + + public static ClassEntry parse(TypeReference typeReference) { + return new ClassEntry(typeReference.getInternalName()); + } + + public static MethodDefEntry parse(MethodDefinition definition) { + ClassEntry classEntry = parse(definition.getDeclaringType()); + MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature()); + Signature signature = Signature.createSignature(definition.getSignature()); + AccessFlags access = new AccessFlags(definition.getModifiers()); + return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null); + } + + public static TypeDescriptor parseTypeDescriptor(TypeReference type) { + return new TypeDescriptor(type.getErasedSignature()); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java new file mode 100644 index 0000000..37bc0c8 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java @@ -0,0 +1,81 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.languages.java.BraceStyle; +import com.strobel.decompiler.languages.java.JavaFormattingOptions; +import com.strobel.decompiler.languages.java.ast.AstBuilder; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.api.EnigmaPluginContext; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.SourceSettings; +import cuchaz.enigma.source.procyon.transformers.*; +import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; +import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; +import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; +import cuchaz.enigma.utils.Utils; + +public class ProcyonDecompiler implements Decompiler { + private final SourceSettings settings; + private final DecompilerSettings decompilerSettings; + private final MetadataSystem metadataSystem; + + public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { + ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); + + metadataSystem = new NoRetryMetadataSystem(typeLoader); + metadataSystem.setEagerMethodLoadingEnabled(true); + + decompilerSettings = DecompilerSettings.javaDefaults(); + decompilerSettings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); + decompilerSettings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); + decompilerSettings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); + decompilerSettings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); + decompilerSettings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); + decompilerSettings.setTypeLoader(typeLoader); + + JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions(); + formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine; + formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine; + formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine; + + this.settings = settings; + } + + @Override + public Source getSource(String className) { + TypeReference type = metadataSystem.lookupType(className); + if (type == null) { + throw new Error(String.format("Unable to find desc: %s", className)); + } + + TypeDefinition resolvedType = type.resolve(); + + DecompilerContext context = new DecompilerContext(); + context.setCurrentType(resolvedType); + context.setSettings(decompilerSettings); + + AstBuilder builder = new AstBuilder(context); + builder.addType(resolvedType); + builder.runTransformations(null); + CompilationUnit source = builder.getCompilationUnit(); + + new ObfuscatedEnumSwitchRewriterTransform(context).run(source); + new VarargsFixer(context).run(source); + new RemoveObjectCasts(context).run(source); + new Java8Generics().run(source); + new InvalidIdentifierFix().run(source); + if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source); + if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source); + source.acceptVisitor(new InsertParenthesesVisitor(), null); + + return new ProcyonSource(source, decompilerSettings); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java new file mode 100644 index 0000000..53c8c70 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.PlainTextOutput; +import com.strobel.decompiler.languages.java.JavaOutputVisitor; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.index.SourceIndexVisitor; +import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform; +import cuchaz.enigma.translation.mapping.EntryRemapper; + +import java.io.StringWriter; + +public class ProcyonSource implements Source { + private final DecompilerSettings settings; + private final CompilationUnit tree; + private String string; + + public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) { + this.settings = settings; + this.tree = tree; + } + + @Override + public SourceIndex index() { + SourceIndex index = new SourceIndex(asString()); + tree.acceptVisitor(new SourceIndexVisitor(), index); + return index; + } + + @Override + public String asString() { + if (string == null) { + StringWriter writer = new StringWriter(); + tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); + string = writer.toString(); + } + + return string; + } + + @Override + public Source addJavadocs(EntryRemapper remapper) { + CompilationUnit remappedTree = (CompilationUnit) tree.clone(); + new AddJavadocsAstTransform(remapper).run(remappedTree); + return new ProcyonSource(remappedTree, settings); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java new file mode 100644 index 0000000..f6eeb15 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * 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.source.procyon.index; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.entry.*; + +public class SourceIndexClassVisitor extends SourceIndexVisitor { + private ClassDefEntry classEntry; + + public SourceIndexClassVisitor(ClassDefEntry classEntry) { + this.classEntry = classEntry; + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + // is this this class, or a subtype? + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassDefEntry classEntry = EntryParser.parse(def); + if (!classEntry.equals(this.classEntry)) { + // it's a subtype, recurse + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + return visitChildren(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(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + MethodDefEntry methodEntry = EntryParser.parse(def); + AstNode tokenNode = node.getNameToken(); + if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { + // for static initializers, check elsewhere for the token node + tokenNode = node.getModifiers().firstOrNullObject(); + } + index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + MethodDefEntry methodEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldDefEntry fieldEntry = EntryParser.parse(def); + assert (node.getVariables().size() == 1); + VariableInitializer variable = node.getVariables().firstOrNullObject(); + index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry); + return visitChildren(node, index); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { + // treat enum declarations as field declarations + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldDefEntry fieldEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry); + return visitChildren(node, index); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java new file mode 100644 index 0000000..0e8bc51 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * 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.source.procyon.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.strobel.assembler.metadata.*; +import com.strobel.decompiler.ast.Variable; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; + +import java.lang.Error; +import java.util.HashMap; +import java.util.Map; + +public class SourceIndexMethodVisitor extends SourceIndexVisitor { + private final MethodDefEntry methodEntry; + + private Multimap unmatchedIdentifier = HashMultimap.create(); + private Map> identifierEntryCache = new HashMap<>(); + + public SourceIndexMethodVisitor(MethodDefEntry methodEntry) { + this.methodEntry = methodEntry; + } + + @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()); + MethodEntry methodEntry = null; + if (ref instanceof MethodReference) { + methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(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(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry); + } + } + + // Check for identifier + node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) + .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); + return visitChildren(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref instanceof FieldReference) { + // 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 = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); + index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry); + } + + return visitChildren(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(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + int parameterIndex = def.getSlot(); + + if (parameterIndex >= 0) { + MethodDefEntry ownerMethod = methodEntry; + if (def.getMethod() instanceof MethodDefinition) { + ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod()); + } + + TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType()); + LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null); + Identifier identifier = node.getNameToken(); + // cache the argument entry and the identifier + identifierEntryCache.put(identifier.getName(), localVariableEntry); + index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); + } + + return visitChildren(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 TypeDescriptor(ref.getErasedSignature())); + index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry); + } else + this.checkIdentifier(node, index); + return visitChildren(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(TokenFactory.createToken(index, 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(TokenFactory.createToken(index, identifier), entry); + unmatchedIdentifier.removeAll(key); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null && node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType) node.getType(); + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry constructorEntry = new MethodEntry(classEntry, "", new MethodDescriptor(ref.getErasedSignature())); + index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry); + } + + return visitChildren(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(); + Variable variable = initializer.getUserData(Keys.VARIABLE); + if (variable != null) { + VariableDefinition originalVariable = variable.getOriginalVariable(); + if (originalVariable != null) { + int variableIndex = originalVariable.getSlot(); + if (variableIndex >= 0) { + MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod()); + TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType()); + LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); + } + } + } + } + } + return visitChildren(node, index); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + if (ref instanceof MethodReference) { + // get the behavior entry + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); + + // get the node for the token + AstNode methodNameToken = node.getMethodNameToken(); + AstNode targetToken = node.getTarget(); + + if (methodNameToken != null) { + index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry); + } + + if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) { + index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry); + } + } + + return visitChildren(node, index); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java new file mode 100644 index 0000000..dad505f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * 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.source.procyon.index; + +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; + +public class SourceIndexVisitor extends DepthFirstAstVisitor { + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassDefEntry classEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); + + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + @Override + protected Void visitChildren(AstNode node, SourceIndex index) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, index); + } + return null; + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java new file mode 100644 index 0000000..db90ffa --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.source.procyon.index; + +import com.strobel.decompiler.languages.Region; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.source.SourceIndex; + +import java.util.regex.Pattern; + +public class TokenFactory { + private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); + + public static Token createToken(SourceIndex index, AstNode node) { + String name = node instanceof Identifier ? ((Identifier) node).getName() : ""; + Region region = node.getRegion(); + + int start = index.getPosition(region.getBeginLine(), region.getBeginColumn()); + int end = index.getPosition(region.getEndLine(), region.getEndColumn()); + String text = index.getSource().substring(start, end); + Token token = new Token(start, end, text); + + boolean isAnonymousInner = + node instanceof Identifier && + name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && + name.lastIndexOf('$') >= 0 && + !ANONYMOUS_INNER.matcher(name).matches(); + + if (isAnonymousInner) { + TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; + if (type != null) { + name = type.getName(); + token.end = token.start + name.length(); + } + } + + return token; + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java new file mode 100644 index 0000000..70fc8c6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java @@ -0,0 +1,134 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.google.common.base.Function; +import com.google.common.base.Strings; +import com.strobel.assembler.metadata.ParameterDefinition; +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public final class AddJavadocsAstTransform implements IAstTransform { + + private final EntryRemapper remapper; + + public AddJavadocsAstTransform(EntryRemapper remapper) { + this.remapper = remapper; + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(remapper), null); + } + + static class Visitor extends DepthFirstAstVisitor { + + private final EntryRemapper remapper; + + Visitor(EntryRemapper remapper) { + this.remapper = remapper; + } + + private void addDoc(T node, Function> retriever) { + final Comment[] comments = getComments(node, retriever); + if (comments != null) { + node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments); + } + } + + private Comment[] getComments(T node, Function> retriever) { + final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); + final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc()); + return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st, + CommentType.Documentation)).toArray(Comment[]::new); + } + + private Comment[] getParameterComments(ParameterDeclaration node, Function> retriever) { + final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); + final Comment[] ret = getComments(node, retriever); + if (ret != null) { + final String paramPrefix = "@param " + mapping.getTargetName() + " "; + final String indent = Strings.repeat(" ", paramPrefix.length()); + ret[0].setContent(paramPrefix + ret[0].getContent()); + for (int i = 1; i < ret.length; i++) { + ret[i].setContent(indent + ret[i].getContent()); + } + } + return ret; + } + + private void visitMethod(AstNode node) { + final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION)); + final Comment[] baseComments = getComments(node, $ -> methodDefEntry); + List comments = new ArrayList<>(); + if (baseComments != null) + Collections.addAll(comments, baseComments); + + for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) { + ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION); + final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(), + true, + EntryParser.parseTypeDescriptor(def.getParameterType()), null)); + if (paramComments != null) + Collections.addAll(comments, paramComments); + } + + if (!comments.isEmpty()) { + if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) { + comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation)); + } + final AstNode oldFirst = node.getFirstChild(); + for (Comment comment : comments) { + node.insertChildBefore(oldFirst, comment, Roles.COMMENT); + } + } + } + + @Override + protected Void visitChildren(AstNode node, Void data) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, data); + } + return null; + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, Void data) { + visitMethod(node); + return super.visitMethodDeclaration(node, data); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) { + visitMethod(node); + return super.visitConstructorDeclaration(node, data); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); + return super.visitFieldDeclaration(node, data); + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION))); + return super.visitTypeDeclaration(node, data); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); + return super.visitEnumValueDeclaration(node, data); + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java new file mode 100644 index 0000000..39e599d --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.ImportDeclaration; +import com.strobel.decompiler.languages.java.ast.PackageDeclaration; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +public final class DropImportAstTransform implements IAstTransform { + public static final DropImportAstTransform INSTANCE = new DropImportAstTransform(); + + private DropImportAstTransform() { + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor { + @Override + public Void visitPackageDeclaration(PackageDeclaration node, Void data) { + node.remove(); + return null; + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, Void data) { + node.remove(); + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java new file mode 100644 index 0000000..b8c087b --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java @@ -0,0 +1,37 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +import javax.lang.model.element.Modifier; + +public final class DropVarModifiersAstTransform implements IAstTransform { + public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform(); + + private DropVarModifiersAstTransform() { + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor { + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, Void data) { + for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) { + if (modifierToken.getModifier() == Modifier.FINAL) { + modifierToken.remove(); + } + } + + return null; + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { + node.removeModifier(Modifier.FINAL); + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java new file mode 100644 index 0000000..34d95fa --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java @@ -0,0 +1,29 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 13/07/2018. + */ +public class InvalidIdentifierFix implements IAstTransform { + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + class Visitor extends DepthFirstAstVisitor{ + @Override + public Void visitIdentifier(Identifier node, Void data) { + super.visitIdentifier(node, data); + if (node.getName().equals("do") || node.getName().equals("if")){ + Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation()); + newIdentifier.copyUserDataFrom(node); + node.replaceWith(newIdentifier); + } + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java new file mode 100644 index 0000000..8accfc7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java @@ -0,0 +1,107 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.assembler.metadata.CommonTypeReferences; +import com.strobel.assembler.metadata.Flags; +import com.strobel.assembler.metadata.IGenericInstance; +import com.strobel.assembler.metadata.IMemberDefinition; +import com.strobel.assembler.metadata.JvmType; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.AstNodeCollection; +import com.strobel.decompiler.languages.java.ast.AstType; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ComposedType; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.Roles; +import com.strobel.decompiler.languages.java.ast.SimpleType; +import com.strobel.decompiler.languages.java.ast.WildcardType; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 12/07/2018. + */ +public class Java8Generics implements IAstTransform { + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor{ + + @Override + public Void visitInvocationExpression(InvocationExpression node, Void data) { + super.visitInvocationExpression(node, data); + if (node.getTarget() instanceof MemberReferenceExpression){ + MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget(); + if (referenceExpression.getTypeArguments().stream().map(t->{ + TypeReference tr = t.toTypeReference(); + if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below + TypeReference resolved = tr.resolve(); + if (resolved != null) + return resolved; + } + return tr; + }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) { + //these are invalid for invocations, let the compiler work it out + referenceExpression.getTypeArguments().clear(); + } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){ + //all are , thereby redundant and/or bad + referenceExpression.getTypeArguments().clear(); + } + } + return null; + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { + super.visitObjectCreationExpression(node, data); + AstType type = node.getType(); + if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){ + SimpleType simpleType = (SimpleType) type; + AstNodeCollection typeArguments = simpleType.getTypeArguments(); + if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){ + //all are , thereby redundant and/or bad + typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create("")); + } + } + return null; + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + boolean doReplace = false; + TypeReference typeReference = node.getType().toTypeReference(); + if (typeReference.isArray() && typeReference.getElementType().isGenericType()){ + doReplace = true; + } else if (typeReference.isGenericType()) { + Expression target = node.getExpression(); + if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){ + doReplace = true; + } else if (target instanceof InvocationExpression) { + InvocationExpression invocationExpression = (InvocationExpression)target; + if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) { + ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear(); + doReplace = true; + } + } + } + super.visitCastExpression(node, data); + if (doReplace){ + node.replaceWith(node.getExpression()); + } + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java new file mode 100644 index 0000000..32bb72f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java @@ -0,0 +1,414 @@ +/* + * Originally: + * EnumSwitchRewriterTransform.java + * + * Copyright (c) 2013 Mike Strobel + * + * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; + * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. + * A copy of the license can be found in the License.html file at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + */ + +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.SafeCloseable; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.AstBuilder; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CaseLabel; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.IndexerExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; +import com.strobel.decompiler.languages.java.ast.SwitchSection; +import com.strobel.decompiler.languages.java.ast.SwitchStatement; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to: + * - Not rely on a field containing "$SwitchMap$" (Proguard strips it) + * - Ignore classes *with* SwitchMap$ names (so the original can handle it) + * - Ignores inner synthetics that are not package private + */ +@SuppressWarnings("Duplicates") +public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform { + private final DecompilerContext _context; + + public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) { + _context = VerifyArgument.notNull(context, "context"); + } + + @Override + public void run(final AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor { + private final static class SwitchMapInfo { + final String enclosingType; + final Map> switches = new LinkedHashMap<>(); + final Map> mappings = new LinkedHashMap<>(); + + TypeDeclaration enclosingTypeDeclaration; + + SwitchMapInfo(final String enclosingType) { + this.enclosingType = enclosingType; + } + } + + private final Map _switchMaps = new LinkedHashMap<>(); + private boolean _isSwitchMapWrapper; + + protected Visitor(final DecompilerContext context) { + super(context); + } + + @Override + public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; + final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); + final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); + + if (isSwitchMapWrapper) { + final String internalName = typeDefinition.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(internalName); + + if (info == null) { + _switchMaps.put(internalName, info = new SwitchMapInfo(internalName)); + } + + info.enclosingTypeDeclaration = typeDeclaration; + } + + _isSwitchMapWrapper = isSwitchMapWrapper; + + try { + super.visitTypeDeclaration(typeDeclaration, p); + } + finally { + _isSwitchMapWrapper = oldIsSwitchMapWrapper; + } + + rewrite(); + + return null; + } + + @Override + public Void visitSwitchStatement(final SwitchStatement node, final Void data) { + final Expression test = node.getExpression(); + + if (test instanceof IndexerExpression) { + final IndexerExpression indexer = (IndexerExpression) test; + final Expression array = indexer.getTarget(); + final Expression argument = indexer.getArgument(); + + if (!(array instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array; + final Expression arrayOwner = arrayAccess.getTarget(); + final String mapName = arrayAccess.getMemberName(); + + if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner; + final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE); + + if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) { + return super.visitSwitchStatement(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + + if (!"ordinal".equals(memberReference.getMemberName())) { + return super.visitSwitchStatement(node, data); + } + + final String enclosingTypeName = enclosingType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingTypeName); + + if (info == null) { + _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName)); + + final TypeDefinition resolvedType = enclosingType.resolve(); + + if (resolvedType != null) { + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + try (final SafeCloseable importSuppression = astBuilder.suppressImports()) { + final TypeDeclaration declaration = astBuilder.createType(resolvedType); + + declaration.acceptVisitor(this, data); + } + } + } + + List switches = info.switches.get(mapName); + + if (switches == null) { + info.switches.put(mapName, switches = new ArrayList<>()); + } + + switches.add(node); + } + + return super.visitSwitchStatement(node, data); + } + + @Override + public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) { + final TypeDefinition currentType = context.getCurrentType(); + final MethodDefinition currentMethod = context.getCurrentMethod(); + + if (_isSwitchMapWrapper && + currentType != null && + currentMethod != null && + currentMethod.isTypeInitializer()) { + + final Expression left = node.getLeft(); + final Expression right = node.getRight(); + + if (left instanceof IndexerExpression && + right instanceof PrimitiveExpression) { + + String mapName = null; + + final Expression array = ((IndexerExpression) left).getTarget(); + final Expression argument = ((IndexerExpression) left).getArgument(); + + if (array instanceof MemberReferenceExpression) { + mapName = ((MemberReferenceExpression) array).getMemberName(); + } + else if (array instanceof IdentifierExpression) { + mapName = ((IdentifierExpression) array).getIdentifier(); + } + + if (mapName == null || mapName.startsWith("$SwitchMap$")) { + return super.visitAssignmentExpression(node, data); + } + + if (!(argument instanceof InvocationExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + final Expression memberTarget = memberReference.getTarget(); + + if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget; + final Expression outerMemberTarget = outerMemberReference.getTarget(); + + if (!(outerMemberTarget instanceof TypeReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final String enclosingType = currentType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingType); + + if (info == null) { + _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType)); + + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + info.enclosingTypeDeclaration = astBuilder.createType(currentType); + } + + final PrimitiveExpression value = (PrimitiveExpression) right; + + assert value.getValue() instanceof Integer; + + Map mapping = info.mappings.get(mapName); + + if (mapping == null) { + info.mappings.put(mapName, mapping = new LinkedHashMap<>()); + } + + final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName()); + + enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE)); + + mapping.put(((Number) value.getValue()).intValue(), enumValue); + } + } + + return super.visitAssignmentExpression(node, data); + } + + private void rewrite() { + if (_switchMaps.isEmpty()) { + return; + } + + for (final SwitchMapInfo info : _switchMaps.values()) { + rewrite(info); + } + + // + // Remove switch map type wrappers that are no longer referenced. + // + + outer: + for (final SwitchMapInfo info : _switchMaps.values()) { + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + + if (switches != null && !switches.isEmpty()) { + continue outer; + } + } + + final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration; + + if (enclosingTypeDeclaration != null) { + enclosingTypeDeclaration.remove(); + } + } + } + + private void rewrite(final SwitchMapInfo info) { + if (info.switches.isEmpty()) { + return; + } + + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + final Map mappings = info.mappings.get(mapName); + + if (switches != null && mappings != null) { + for (int i = 0; i < switches.size(); i++) { + if (rewriteSwitch(switches.get(i), mappings)) { + switches.remove(i--); + } + } + } + } + } + + private boolean rewriteSwitch(final SwitchStatement s, final Map mappings) { + final Map replacements = new IdentityHashMap<>(); + + for (final SwitchSection section : s.getSwitchSections()) { + for (final CaseLabel caseLabel : section.getCaseLabels()) { + final Expression expression = caseLabel.getExpression(); + + if (expression.isNull()) { + continue; + } + + if (expression instanceof PrimitiveExpression) { + final Object value = ((PrimitiveExpression) expression).getValue(); + + if (value instanceof Integer) { + final Expression replacement = mappings.get(value); + + if (replacement != null) { + replacements.put(expression, replacement); + continue; + } + } + } + + // + // If we can't rewrite all cases, we abort. + // + + return false; + } + } + + final IndexerExpression indexer = (IndexerExpression) s.getExpression(); + final InvocationExpression argument = (InvocationExpression) indexer.getArgument(); + final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget(); + final Expression newTest = memberReference.getTarget(); + + newTest.remove(); + indexer.replaceWith(newTest); + + for (final Map.Entry entry : replacements.entrySet()) { + entry.getKey().replaceWith(entry.getValue().clone()); + } + + return true; + } + + private static boolean isSwitchMapWrapper(final TypeReference type) { + if (type == null) { + return false; + } + + final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type + : type.resolve(); + + if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) { + return false; + } + + for (final FieldDefinition field : definition.getDeclaredFields()) { + if (!field.getName().startsWith("$SwitchMap$") && + BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) { + + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java new file mode 100644 index 0000000..cf0376f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 11/07/2018. + */ +public class RemoveObjectCasts implements IAstTransform { + private final DecompilerContext _context; + + public RemoveObjectCasts(DecompilerContext context) { + _context = context; + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor{ + + protected Visitor(DecompilerContext context) { + super(context); + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){ + node.replaceWith(node.getExpression()); + } + return super.visitCastExpression(node, data); + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java new file mode 100644 index 0000000..d3ddaab --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java @@ -0,0 +1,197 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MetadataFilters; +import com.strobel.assembler.metadata.MetadataHelper; +import com.strobel.assembler.metadata.MethodBinder; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.MethodReference; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.StringUtilities; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.AstNodeCollection; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.JavaResolver; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; +import com.strobel.decompiler.semantics.ResolveResult; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Thiakil on 12/07/2018. + */ +public class VarargsFixer implements IAstTransform { + private final DecompilerContext _context; + + public VarargsFixer(final DecompilerContext context) { + _context = VerifyArgument.notNull(context, "context"); + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + class Visitor extends ContextTrackingVisitor { + private final JavaResolver _resolver; + protected Visitor(DecompilerContext context) { + super(context); + _resolver = new JavaResolver(context); + } + + //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them + @Override + public Void visitInvocationExpression(InvocationExpression node, Void data) { + super.visitInvocationExpression(node, data); + MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE); + if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){ + AstNodeCollection arguments = node.getArguments(); + Expression lastParam = arguments.lastOrNullObject(); + if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){ + ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam; + if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){ + lastParam.remove(); + } else { + for (Expression e : varargArray.getInitializer().getElements()){ + arguments.insertBefore(varargArray, e.clone()); + } + varargArray.remove(); + } + } + } + return null; + } + + //applies the vararg transform to object creation + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { + super.visitObjectCreationExpression(node, data); + final AstNodeCollection arguments = node.getArguments(); + final Expression lastArgument = arguments.lastOrNullObject(); + + Expression arrayArg = lastArgument; + + if (arrayArg instanceof CastExpression) + arrayArg = ((CastExpression) arrayArg).getExpression(); + + if (arrayArg == null || + arrayArg.isNull() || + !(arrayArg instanceof ArrayCreationExpression && + node.getTarget() instanceof MemberReferenceExpression)) { + + return null; + } + + final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg; + final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget(); + + if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) { + return null; + } + + final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE); + + if (method == null) { + return null; + } + + final MethodDefinition resolved = method.resolve(); + + if (resolved == null || !resolved.isVarArgs()) { + return null; + } + + final List candidates; + final Expression invocationTarget = target.getTarget(); + + if (invocationTarget == null || invocationTarget.isNull()) { + candidates = MetadataHelper.findMethods( + context.getCurrentType(), + MetadataFilters.matchName(resolved.getName()) + ); + } + else { + final ResolveResult targetResult = _resolver.apply(invocationTarget); + + if (targetResult == null || targetResult.getType() == null) { + return null; + } + + candidates = MetadataHelper.findMethods( + targetResult.getType(), + MetadataFilters.matchName(resolved.getName()) + ); + } + + final List argTypes = new ArrayList<>(); + + for (final Expression argument : arguments) { + final ResolveResult argResult = _resolver.apply(argument); + + if (argResult == null || argResult.getType() == null) { + return null; + } + + argTypes.add(argResult.getType()); + } + + final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes); + + if (c1.isFailure() || c1.isAmbiguous()) { + return null; + } + + argTypes.remove(argTypes.size() - 1); + + final ArrayInitializerExpression initializer = newArray.getInitializer(); + final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty(); + + if (hasElements) { + for (final Expression argument : initializer.getElements()) { + final ResolveResult argResult = _resolver.apply(argument); + + if (argResult == null || argResult.getType() == null) { + return null; + } + + argTypes.add(argResult.getType()); + } + } + + final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes); + + if (c2.isFailure() || + c2.isAmbiguous() || + !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) { + + return null; + } + + lastArgument.remove(); + + if (!hasElements) { + lastArgument.remove(); + return null; + } + + for (final Expression newArg : initializer.getElements()) { + newArg.remove(); + arguments.add(newArg); + } + + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java new file mode 100644 index 0000000..e702956 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ClasspathTypeLoader; +import com.strobel.assembler.metadata.ITypeLoader; + +/** + * Caching version of {@link ClasspathTypeLoader} + */ +public class CachingClasspathTypeLoader extends CachingTypeLoader { + private static ITypeLoader extraClassPathLoader = null; + + public static void setExtraClassPathLoader(ITypeLoader loader){ + extraClassPathLoader = loader; + } + + private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); + + @Override + protected byte[] doLoad(String className) { + Buffer parentBuf = new Buffer(); + if (classpathLoader.tryLoadType(className, parentBuf)) { + return parentBuf.array(); + } + if (extraClassPathLoader != null){ + parentBuf.reset(); + if (extraClassPathLoader.tryLoadType(className, parentBuf)){ + return parentBuf.array(); + } + } + return EMPTY_ARRAY;//need to return *something* as null means no store + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java new file mode 100644 index 0000000..5be5ddd --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.google.common.collect.Maps; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +import java.util.Map; + +/** + * Common cache functions + */ +public abstract class CachingTypeLoader implements ITypeLoader { + protected static final byte[] EMPTY_ARRAY = {}; + + private final Map cache = Maps.newHashMap(); + + protected abstract byte[] doLoad(String className); + + @Override + public boolean tryLoadType(String className, Buffer out) { + + // check the cache + byte[] data = this.cache.computeIfAbsent(className, this::doLoad); + + if (data == EMPTY_ARRAY) { + return false; + } + + out.reset(data.length); + System.arraycopy(data, 0, out.array(), out.position(), data.length); + out.position(0); + return true; + } + + public void clearCache() { + this.cache.clear(); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java new file mode 100644 index 0000000..e703d3b --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * 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.source.procyon.typeloader; + +import com.google.common.collect.Lists; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +public class CompiledSourceTypeLoader extends CachingTypeLoader { + //Store one instance as the classpath shouldn't change during load + private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); + + private final ClassProvider compiledSource; + private final LinkedList> visitors = new LinkedList<>(); + + public CompiledSourceTypeLoader(ClassProvider compiledSource) { + this.compiledSource = compiledSource; + } + + public void addVisitor(Function visitor) { + this.visitors.addFirst(visitor); + } + + @Override + protected byte[] doLoad(String className) { + byte[] data = loadType(className); + if (data == null) { + return loadClasspath(className); + } + + return data; + } + + private byte[] loadClasspath(String name) { + Buffer parentBuf = new Buffer(); + if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { + return parentBuf.array(); + } + return EMPTY_ARRAY; + } + + private byte[] loadType(String className) { + ClassEntry entry = new ClassEntry(className); + + // find the class in the jar + ClassNode node = findClassNode(entry); + if (node == null) { + // couldn't find it + return null; + } + + removeRedundantClassCalls(node); + + ClassWriter writer = new ClassWriter(0); + + ClassVisitor visitor = writer; + for (Function visitorFunction : this.visitors) { + visitor = visitorFunction.apply(visitor); + } + + node.accept(visitor); + + // we have a transformed class! + return writer.toByteArray(); + } + + private void removeRedundantClassCalls(ClassNode node) { + // remove .getClass() calls that are seemingly injected + // DUP + // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; + // POP + for (MethodNode methodNode : node.methods) { + AbstractInsnNode insnNode = methodNode.instructions.getFirst(); + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { + AbstractInsnNode previous = methodInsnNode.getPrevious(); + AbstractInsnNode next = methodInsnNode.getNext(); + if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { + insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction + methodNode.instructions.remove(previous); + methodNode.instructions.remove(methodInsnNode); + methodNode.instructions.remove(next); + } + } + } + insnNode = insnNode.getNext(); + } + } + } + + private ClassNode findClassNode(ClassEntry entry) { + // try to find the class in the jar + for (String className : getClassNamesToTry(entry)) { + ClassNode node = compiledSource.getClassNode(className); + if (node != null) { + return node; + } + } + + // didn't find it ;_; + return null; + } + + private Collection getClassNamesToTry(ClassEntry entry) { + List classNamesToTry = Lists.newArrayList(); + classNamesToTry.add(entry.getFullName()); + + ClassEntry outerClass = entry.getOuterClass(); + if (outerClass != null) { + classNamesToTry.addAll(getClassNamesToTry(outerClass)); + } + + return classNamesToTry; + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java new file mode 100644 index 0000000..c4732b0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public final class NoRetryMetadataSystem extends MetadataSystem { + private final Set failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public NoRetryMetadataSystem(final ITypeLoader typeLoader) { + super(typeLoader); + } + + @Override + protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { + if (failedTypes.contains(descriptor)) { + return null; + } + + final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); + + if (result == null) { + failedTypes.add(descriptor); + } + + return result; + } + + @Override + public synchronized TypeDefinition resolve(final TypeReference type) { + return super.resolve(type); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java new file mode 100644 index 0000000..86c6ecc --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java @@ -0,0 +1,20 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +/** + * Typeloader with synchronized tryLoadType method + */ +public class SynchronizedTypeLoader implements ITypeLoader { + private final ITypeLoader delegate; + + public SynchronizedTypeLoader(ITypeLoader delegate) { + this.delegate = delegate; + } + + @Override + public synchronized boolean tryLoadType(String internalName, Buffer buffer) { + return delegate.tryLoadType(internalName, buffer); + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java index 719d693..f7ba849 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java +++ b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java @@ -13,7 +13,6 @@ package cuchaz.enigma.translation.representation; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; -import com.strobel.assembler.metadata.TypeReference; import cuchaz.enigma.translation.Translatable; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMapping; @@ -112,10 +111,6 @@ public class TypeDescriptor implements Translatable { return new TypeDescriptor("L" + name + ";"); } - public static TypeDescriptor parse(TypeReference type) { - return new TypeDescriptor(type.getErasedSignature()); - } - @Override public String toString() { return this.desc; diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java index 4b245bc..6930765 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java @@ -12,7 +12,6 @@ package cuchaz.enigma.translation.representation.entry; import com.google.common.base.Preconditions; -import com.strobel.assembler.metadata.TypeDefinition; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.representation.AccessFlags; @@ -53,15 +52,6 @@ public class ClassDefEntry extends ClassEntry implements DefEntry { return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses); } - public static ClassDefEntry parse(TypeDefinition def) { - String name = def.getInternalName(); - Signature signature = Signature.createSignature(def.getSignature()); - AccessFlags access = new AccessFlags(def.getModifiers()); - ClassEntry superClass = def.getBaseType() != null ? ClassEntry.parse(def.getBaseType()) : null; - ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(ClassEntry::parse).toArray(ClassEntry[]::new); - return new ClassDefEntry(name, signature, access, superClass, interfaces); - } - public Signature getSignature() { return signature; } diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java index 23ce4a2..74298e4 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java @@ -11,7 +11,6 @@ package cuchaz.enigma.translation.representation.entry; -import com.strobel.assembler.metadata.TypeReference; import cuchaz.enigma.throwables.IllegalNameException; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMapping; @@ -47,10 +46,6 @@ public class ClassEntry extends ParentedEntry implements Comparable< } } - public static ClassEntry parse(TypeReference typeReference) { - return new ClassEntry(typeReference.getInternalName()); - } - @Override public Class getParentType() { return ClassEntry.class; diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java index 46c0b00..f9282b2 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java @@ -12,7 +12,6 @@ package cuchaz.enigma.translation.representation.entry; import com.google.common.base.Preconditions; -import com.strobel.assembler.metadata.FieldDefinition; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.representation.AccessFlags; @@ -41,14 +40,6 @@ public class FieldDefEntry extends FieldEntry implements DefEntry { return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access), null); } - public static FieldDefEntry parse(FieldDefinition definition) { - ClassEntry owner = ClassEntry.parse(definition.getDeclaringType()); - TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature()); - Signature signature = Signature.createTypedSignature(definition.getSignature()); - AccessFlags access = new AccessFlags(definition.getModifiers()); - return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null); - } - @Override public AccessFlags getAccess() { return access; diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java index 280b605..4e75a5c 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java @@ -12,7 +12,6 @@ package cuchaz.enigma.translation.representation.entry; import com.google.common.base.Preconditions; -import com.strobel.assembler.metadata.MethodDefinition; import cuchaz.enigma.translation.Translator; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.representation.AccessFlags; @@ -41,14 +40,6 @@ public class MethodDefEntry extends MethodEntry implements DefEntry return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access), null); } - public static MethodDefEntry parse(MethodDefinition definition) { - ClassEntry classEntry = ClassEntry.parse(definition.getDeclaringType()); - MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature()); - Signature signature = Signature.createSignature(definition.getSignature()); - AccessFlags access = new AccessFlags(definition.getModifiers()); - return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null); - } - @Override public AccessFlags getAccess() { return access; diff --git a/src/main/java/cuchaz/enigma/utils/Pair.java b/src/main/java/cuchaz/enigma/utils/Pair.java new file mode 100644 index 0000000..bf02cef --- /dev/null +++ b/src/main/java/cuchaz/enigma/utils/Pair.java @@ -0,0 +1,26 @@ +package cuchaz.enigma.utils; + +import java.util.Objects; + +public class Pair { + public final A a; + public final B b; + + public Pair(A a, B b) { + this.a = a; + this.b = b; + } + + @Override + public int hashCode() { + return Objects.hashCode(a) * 31 + + Objects.hashCode(b); + } + + @Override + public boolean equals(Object o) { + return o instanceof Pair && + Objects.equals(a, ((Pair) o).a) && + Objects.equals(b, ((Pair) o).b); + } +} -- cgit v1.2.3