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/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 +
20 files changed, 1832 insertions(+)
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
(limited to 'src/main/java/cuchaz/enigma/source/procyon')
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