summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/source/procyon
diff options
context:
space:
mode:
authorGravatar Runemoro2020-03-09 06:04:08 -0400
committerGravatar GitHub2020-03-09 10:04:08 +0000
commit58c0aeb15a65324de08a914dfa62cc68a516a4e3 (patch)
treef45e8141c0864692051149a478c5a0a6bbe68686 /src/main/java/cuchaz/enigma/source/procyon
parentMade Enigma gui translatable (#193) (diff)
downloadenigma-fork-58c0aeb15a65324de08a914dfa62cc68a516a4e3.tar.gz
enigma-fork-58c0aeb15a65324de08a914dfa62cc68a516a4e3.tar.xz
enigma-fork-58c0aeb15a65324de08a914dfa62cc68a516a4e3.zip
CFR support (#192)
* Add decompiler API * Add CFR support
Diffstat (limited to 'src/main/java/cuchaz/enigma/source/procyon')
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/EntryParser.java49
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java81
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java49
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java95
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java218
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java40
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java41
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java134
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java33
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java37
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java29
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java107
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java414
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java39
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java197
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java33
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java38
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java140
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java38
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java20
20 files changed, 1832 insertions, 0 deletions
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 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.assembler.metadata.FieldDefinition;
4import com.strobel.assembler.metadata.MethodDefinition;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7import cuchaz.enigma.translation.representation.AccessFlags;
8import cuchaz.enigma.translation.representation.MethodDescriptor;
9import cuchaz.enigma.translation.representation.Signature;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
14import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
15
16public class EntryParser {
17 public static FieldDefEntry parse(FieldDefinition definition) {
18 ClassEntry owner = parse(definition.getDeclaringType());
19 TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature());
20 Signature signature = Signature.createTypedSignature(definition.getSignature());
21 AccessFlags access = new AccessFlags(definition.getModifiers());
22 return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null);
23 }
24
25 public static ClassDefEntry parse(TypeDefinition def) {
26 String name = def.getInternalName();
27 Signature signature = Signature.createSignature(def.getSignature());
28 AccessFlags access = new AccessFlags(def.getModifiers());
29 ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null;
30 ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new);
31 return new ClassDefEntry(name, signature, access, superClass, interfaces);
32 }
33
34 public static ClassEntry parse(TypeReference typeReference) {
35 return new ClassEntry(typeReference.getInternalName());
36 }
37
38 public static MethodDefEntry parse(MethodDefinition definition) {
39 ClassEntry classEntry = parse(definition.getDeclaringType());
40 MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature());
41 Signature signature = Signature.createSignature(definition.getSignature());
42 AccessFlags access = new AccessFlags(definition.getModifiers());
43 return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null);
44 }
45
46 public static TypeDescriptor parseTypeDescriptor(TypeReference type) {
47 return new TypeDescriptor(type.getErasedSignature());
48 }
49}
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 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import com.strobel.assembler.metadata.MetadataSystem;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7import com.strobel.decompiler.DecompilerContext;
8import com.strobel.decompiler.DecompilerSettings;
9import com.strobel.decompiler.languages.java.BraceStyle;
10import com.strobel.decompiler.languages.java.JavaFormattingOptions;
11import com.strobel.decompiler.languages.java.ast.AstBuilder;
12import com.strobel.decompiler.languages.java.ast.CompilationUnit;
13import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
14import cuchaz.enigma.ClassProvider;
15import cuchaz.enigma.api.EnigmaPluginContext;
16import cuchaz.enigma.source.Source;
17import cuchaz.enigma.source.Decompiler;
18import cuchaz.enigma.source.SourceSettings;
19import cuchaz.enigma.source.procyon.transformers.*;
20import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader;
21import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem;
22import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader;
23import cuchaz.enigma.utils.Utils;
24
25public class ProcyonDecompiler implements Decompiler {
26 private final SourceSettings settings;
27 private final DecompilerSettings decompilerSettings;
28 private final MetadataSystem metadataSystem;
29
30 public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) {
31 ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider));
32
33 metadataSystem = new NoRetryMetadataSystem(typeLoader);
34 metadataSystem.setEagerMethodLoadingEnabled(true);
35
36 decompilerSettings = DecompilerSettings.javaDefaults();
37 decompilerSettings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true));
38 decompilerSettings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true));
39 decompilerSettings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true));
40 decompilerSettings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
41 decompilerSettings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
42 decompilerSettings.setTypeLoader(typeLoader);
43
44 JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions();
45 formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine;
46 formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine;
47 formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine;
48
49 this.settings = settings;
50 }
51
52 @Override
53 public Source getSource(String className) {
54 TypeReference type = metadataSystem.lookupType(className);
55 if (type == null) {
56 throw new Error(String.format("Unable to find desc: %s", className));
57 }
58
59 TypeDefinition resolvedType = type.resolve();
60
61 DecompilerContext context = new DecompilerContext();
62 context.setCurrentType(resolvedType);
63 context.setSettings(decompilerSettings);
64
65 AstBuilder builder = new AstBuilder(context);
66 builder.addType(resolvedType);
67 builder.runTransformations(null);
68 CompilationUnit source = builder.getCompilationUnit();
69
70 new ObfuscatedEnumSwitchRewriterTransform(context).run(source);
71 new VarargsFixer(context).run(source);
72 new RemoveObjectCasts(context).run(source);
73 new Java8Generics().run(source);
74 new InvalidIdentifierFix().run(source);
75 if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source);
76 if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source);
77 source.acceptVisitor(new InsertParenthesesVisitor(), null);
78
79 return new ProcyonSource(source, decompilerSettings);
80 }
81}
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 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.decompiler.DecompilerSettings;
4import com.strobel.decompiler.PlainTextOutput;
5import com.strobel.decompiler.languages.java.JavaOutputVisitor;
6import com.strobel.decompiler.languages.java.ast.CompilationUnit;
7import cuchaz.enigma.source.Source;
8import cuchaz.enigma.source.SourceIndex;
9import cuchaz.enigma.source.procyon.index.SourceIndexVisitor;
10import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform;
11import cuchaz.enigma.translation.mapping.EntryRemapper;
12
13import java.io.StringWriter;
14
15public class ProcyonSource implements Source {
16 private final DecompilerSettings settings;
17 private final CompilationUnit tree;
18 private String string;
19
20 public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) {
21 this.settings = settings;
22 this.tree = tree;
23 }
24
25 @Override
26 public SourceIndex index() {
27 SourceIndex index = new SourceIndex(asString());
28 tree.acceptVisitor(new SourceIndexVisitor(), index);
29 return index;
30 }
31
32 @Override
33 public String asString() {
34 if (string == null) {
35 StringWriter writer = new StringWriter();
36 tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null);
37 string = writer.toString();
38 }
39
40 return string;
41 }
42
43 @Override
44 public Source addJavadocs(EntryRemapper remapper) {
45 CompilationUnit remappedTree = (CompilationUnit) tree.clone();
46 new AddJavadocsAstTransform(remapper).run(remappedTree);
47 return new ProcyonSource(remappedTree, settings);
48 }
49}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.strobel.assembler.metadata.FieldDefinition;
15import com.strobel.assembler.metadata.MethodDefinition;
16import com.strobel.assembler.metadata.TypeDefinition;
17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.source.SourceIndex;
21import cuchaz.enigma.source.procyon.EntryParser;
22import cuchaz.enigma.translation.representation.entry.*;
23
24public class SourceIndexClassVisitor extends SourceIndexVisitor {
25 private ClassDefEntry classEntry;
26
27 public SourceIndexClassVisitor(ClassDefEntry classEntry) {
28 this.classEntry = classEntry;
29 }
30
31 @Override
32 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
33 // is this this class, or a subtype?
34 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
35 ClassDefEntry classEntry = EntryParser.parse(def);
36 if (!classEntry.equals(this.classEntry)) {
37 // it's a subtype, recurse
38 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry);
39 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
40 }
41
42 return visitChildren(node, index);
43 }
44
45 @Override
46 public Void visitSimpleType(SimpleType node, SourceIndex index) {
47 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
48 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
49 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
50 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry);
51 }
52
53 return visitChildren(node, index);
54 }
55
56 @Override
57 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
58 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
59 MethodDefEntry methodEntry = EntryParser.parse(def);
60 AstNode tokenNode = node.getNameToken();
61 if (methodEntry.isConstructor() && methodEntry.getName().equals("<clinit>")) {
62 // for static initializers, check elsewhere for the token node
63 tokenNode = node.getModifiers().firstOrNullObject();
64 }
65 index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry);
66 return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
67 }
68
69 @Override
70 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
71 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
72 MethodDefEntry methodEntry = EntryParser.parse(def);
73 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry);
74 return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
75 }
76
77 @Override
78 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
79 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
80 FieldDefEntry fieldEntry = EntryParser.parse(def);
81 assert (node.getVariables().size() == 1);
82 VariableInitializer variable = node.getVariables().firstOrNullObject();
83 index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry);
84 return visitChildren(node, index);
85 }
86
87 @Override
88 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
89 // treat enum declarations as field declarations
90 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
91 FieldDefEntry fieldEntry = EntryParser.parse(def);
92 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry);
93 return visitChildren(node, index);
94 }
95}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import com.strobel.assembler.metadata.*;
17import com.strobel.decompiler.ast.Variable;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.source.SourceIndex;
21import cuchaz.enigma.source.procyon.EntryParser;
22import cuchaz.enigma.translation.representation.MethodDescriptor;
23import cuchaz.enigma.translation.representation.TypeDescriptor;
24import cuchaz.enigma.translation.representation.entry.*;
25
26import java.lang.Error;
27import java.util.HashMap;
28import java.util.Map;
29
30public class SourceIndexMethodVisitor extends SourceIndexVisitor {
31 private final MethodDefEntry methodEntry;
32
33 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create();
34 private Map<String, Entry<?>> identifierEntryCache = new HashMap<>();
35
36 public SourceIndexMethodVisitor(MethodDefEntry methodEntry) {
37 this.methodEntry = methodEntry;
38 }
39
40 @Override
41 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
42 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
43
44 // get the behavior entry
45 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
46 MethodEntry methodEntry = null;
47 if (ref instanceof MethodReference) {
48 methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
49 }
50 if (methodEntry != null) {
51 // get the node for the token
52 AstNode tokenNode = null;
53 if (node.getTarget() instanceof MemberReferenceExpression) {
54 tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken();
55 } else if (node.getTarget() instanceof SuperReferenceExpression) {
56 tokenNode = node.getTarget();
57 } else if (node.getTarget() instanceof ThisReferenceExpression) {
58 tokenNode = node.getTarget();
59 }
60 if (tokenNode != null) {
61 index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry);
62 }
63 }
64
65 // Check for identifier
66 node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression)
67 .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index));
68 return visitChildren(node, index);
69 }
70
71 @Override
72 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
73 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
74 if (ref instanceof FieldReference) {
75 // make sure this is actually a field
76 String erasedSignature = ref.getErasedSignature();
77 if (erasedSignature.indexOf('(') >= 0) {
78 throw new Error("Expected a field here! got " + ref);
79 }
80
81 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
82 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
83 index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry);
84 }
85
86 return visitChildren(node, index);
87 }
88
89 @Override
90 public Void visitSimpleType(SimpleType node, SourceIndex index) {
91 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
92 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
93 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
94 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry);
95 }
96
97 return visitChildren(node, index);
98 }
99
100 @Override
101 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
102 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
103 int parameterIndex = def.getSlot();
104
105 if (parameterIndex >= 0) {
106 MethodDefEntry ownerMethod = methodEntry;
107 if (def.getMethod() instanceof MethodDefinition) {
108 ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod());
109 }
110
111 TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType());
112 LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null);
113 Identifier identifier = node.getNameToken();
114 // cache the argument entry and the identifier
115 identifierEntryCache.put(identifier.getName(), localVariableEntry);
116 index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry);
117 }
118
119 return visitChildren(node, index);
120 }
121
122 @Override
123 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
124 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
125 if (ref != null) {
126 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
127 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
128 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry);
129 } else
130 this.checkIdentifier(node, index);
131 return visitChildren(node, index);
132 }
133
134 private void checkIdentifier(IdentifierExpression node, SourceIndex index) {
135 if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token!
136 index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier()));
137 else
138 unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it!
139 }
140
141 private void addDeclarationToUnmatched(String key, SourceIndex index) {
142 Entry<?> entry = identifierEntryCache.get(key);
143
144 // This cannot happened in theory
145 if (entry == null)
146 return;
147 for (Identifier identifier : unmatchedIdentifier.get(key))
148 index.addDeclaration(TokenFactory.createToken(index, identifier), entry);
149 unmatchedIdentifier.removeAll(key);
150 }
151
152 @Override
153 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
154 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
155 if (ref != null && node.getType() instanceof SimpleType) {
156 SimpleType simpleTypeNode = (SimpleType) node.getType();
157 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
158 MethodEntry constructorEntry = new MethodEntry(classEntry, "<init>", new MethodDescriptor(ref.getErasedSignature()));
159 index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry);
160 }
161
162 return visitChildren(node, index);
163 }
164
165 @Override
166 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
167 AstNodeCollection<VariableInitializer> variables = node.getVariables();
168
169 // Single assignation
170 if (variables.size() == 1) {
171 VariableInitializer initializer = variables.firstOrNullObject();
172 if (initializer != null && node.getType() instanceof SimpleType) {
173 Identifier identifier = initializer.getNameToken();
174 Variable variable = initializer.getUserData(Keys.VARIABLE);
175 if (variable != null) {
176 VariableDefinition originalVariable = variable.getOriginalVariable();
177 if (originalVariable != null) {
178 int variableIndex = originalVariable.getSlot();
179 if (variableIndex >= 0) {
180 MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod());
181 TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType());
182 LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null);
183 identifierEntryCache.put(identifier.getName(), localVariableEntry);
184 addDeclarationToUnmatched(identifier.getName(), index);
185 index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry);
186 }
187 }
188 }
189 }
190 }
191 return visitChildren(node, index);
192 }
193
194 @Override
195 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
196 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
197
198 if (ref instanceof MethodReference) {
199 // get the behavior entry
200 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
201 MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
202
203 // get the node for the token
204 AstNode methodNameToken = node.getMethodNameToken();
205 AstNode targetToken = node.getTarget();
206
207 if (methodNameToken != null) {
208 index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry);
209 }
210
211 if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) {
212 index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry);
213 }
214 }
215
216 return visitChildren(node, index);
217 }
218}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.strobel.assembler.metadata.TypeDefinition;
15import com.strobel.decompiler.languages.java.ast.AstNode;
16import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
17import com.strobel.decompiler.languages.java.ast.Keys;
18import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
19import cuchaz.enigma.source.SourceIndex;
20import cuchaz.enigma.source.procyon.EntryParser;
21import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
22
23public class SourceIndexVisitor extends DepthFirstAstVisitor<SourceIndex, Void> {
24 @Override
25 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
26 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
27 ClassDefEntry classEntry = EntryParser.parse(def);
28 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry);
29
30 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
31 }
32
33 @Override
34 protected Void visitChildren(AstNode node, SourceIndex index) {
35 for (final AstNode child : node.getChildren()) {
36 child.acceptVisitor(this, index);
37 }
38 return null;
39 }
40}
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 @@
1package cuchaz.enigma.source.procyon.index;
2
3import com.strobel.decompiler.languages.Region;
4import com.strobel.decompiler.languages.java.ast.AstNode;
5import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
6import com.strobel.decompiler.languages.java.ast.Identifier;
7import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
8import cuchaz.enigma.analysis.Token;
9import cuchaz.enigma.source.SourceIndex;
10
11import java.util.regex.Pattern;
12
13public class TokenFactory {
14 private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$");
15
16 public static Token createToken(SourceIndex index, AstNode node) {
17 String name = node instanceof Identifier ? ((Identifier) node).getName() : "";
18 Region region = node.getRegion();
19
20 int start = index.getPosition(region.getBeginLine(), region.getBeginColumn());
21 int end = index.getPosition(region.getEndLine(), region.getEndColumn());
22 String text = index.getSource().substring(start, end);
23 Token token = new Token(start, end, text);
24
25 boolean isAnonymousInner =
26 node instanceof Identifier &&
27 name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration &&
28 name.lastIndexOf('$') >= 0 &&
29 !ANONYMOUS_INNER.matcher(name).matches();
30
31 if (isAnonymousInner) {
32 TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null;
33 if (type != null) {
34 name = type.getName();
35 token.end = token.start + name.length();
36 }
37 }
38
39 return token;
40 }
41}
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 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.google.common.base.Function;
4import com.google.common.base.Strings;
5import com.strobel.assembler.metadata.ParameterDefinition;
6import com.strobel.decompiler.languages.java.ast.*;
7import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
8import cuchaz.enigma.source.procyon.EntryParser;
9import cuchaz.enigma.translation.mapping.EntryMapping;
10import cuchaz.enigma.translation.mapping.EntryRemapper;
11import cuchaz.enigma.translation.mapping.ResolutionStrategy;
12import cuchaz.enigma.translation.representation.entry.*;
13
14import java.util.ArrayList;
15import java.util.Collections;
16import java.util.List;
17import java.util.Objects;
18import java.util.stream.Stream;
19
20public final class AddJavadocsAstTransform implements IAstTransform {
21
22 private final EntryRemapper remapper;
23
24 public AddJavadocsAstTransform(EntryRemapper remapper) {
25 this.remapper = remapper;
26 }
27
28 @Override
29 public void run(AstNode compilationUnit) {
30 compilationUnit.acceptVisitor(new Visitor(remapper), null);
31 }
32
33 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
34
35 private final EntryRemapper remapper;
36
37 Visitor(EntryRemapper remapper) {
38 this.remapper = remapper;
39 }
40
41 private <T extends AstNode> void addDoc(T node, Function<T, Entry<?>> retriever) {
42 final Comment[] comments = getComments(node, retriever);
43 if (comments != null) {
44 node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments);
45 }
46 }
47
48 private <T extends AstNode> Comment[] getComments(T node, Function<T, Entry<?>> retriever) {
49 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node));
50 final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc());
51 return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st,
52 CommentType.Documentation)).toArray(Comment[]::new);
53 }
54
55 private Comment[] getParameterComments(ParameterDeclaration node, Function<ParameterDeclaration, Entry<?>> retriever) {
56 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node));
57 final Comment[] ret = getComments(node, retriever);
58 if (ret != null) {
59 final String paramPrefix = "@param " + mapping.getTargetName() + " ";
60 final String indent = Strings.repeat(" ", paramPrefix.length());
61 ret[0].setContent(paramPrefix + ret[0].getContent());
62 for (int i = 1; i < ret.length; i++) {
63 ret[i].setContent(indent + ret[i].getContent());
64 }
65 }
66 return ret;
67 }
68
69 private void visitMethod(AstNode node) {
70 final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION));
71 final Comment[] baseComments = getComments(node, $ -> methodDefEntry);
72 List<Comment> comments = new ArrayList<>();
73 if (baseComments != null)
74 Collections.addAll(comments, baseComments);
75
76 for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) {
77 ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION);
78 final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(),
79 true,
80 EntryParser.parseTypeDescriptor(def.getParameterType()), null));
81 if (paramComments != null)
82 Collections.addAll(comments, paramComments);
83 }
84
85 if (!comments.isEmpty()) {
86 if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) {
87 comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation));
88 }
89 final AstNode oldFirst = node.getFirstChild();
90 for (Comment comment : comments) {
91 node.insertChildBefore(oldFirst, comment, Roles.COMMENT);
92 }
93 }
94 }
95
96 @Override
97 protected Void visitChildren(AstNode node, Void data) {
98 for (final AstNode child : node.getChildren()) {
99 child.acceptVisitor(this, data);
100 }
101 return null;
102 }
103
104 @Override
105 public Void visitMethodDeclaration(MethodDeclaration node, Void data) {
106 visitMethod(node);
107 return super.visitMethodDeclaration(node, data);
108 }
109
110 @Override
111 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) {
112 visitMethod(node);
113 return super.visitConstructorDeclaration(node, data);
114 }
115
116 @Override
117 public Void visitFieldDeclaration(FieldDeclaration node, Void data) {
118 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION)));
119 return super.visitFieldDeclaration(node, data);
120 }
121
122 @Override
123 public Void visitTypeDeclaration(TypeDeclaration node, Void data) {
124 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION)));
125 return super.visitTypeDeclaration(node, data);
126 }
127
128 @Override
129 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) {
130 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION)));
131 return super.visitEnumValueDeclaration(node, data);
132 }
133 }
134}
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 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.AstNode;
4import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
5import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
6import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
7import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
8
9public final class DropImportAstTransform implements IAstTransform {
10 public static final DropImportAstTransform INSTANCE = new DropImportAstTransform();
11
12 private DropImportAstTransform() {
13 }
14
15 @Override
16 public void run(AstNode compilationUnit) {
17 compilationUnit.acceptVisitor(new Visitor(), null);
18 }
19
20 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
21 @Override
22 public Void visitPackageDeclaration(PackageDeclaration node, Void data) {
23 node.remove();
24 return null;
25 }
26
27 @Override
28 public Void visitImportDeclaration(ImportDeclaration node, Void data) {
29 node.remove();
30 return null;
31 }
32 }
33}
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 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.*;
4import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
5
6import javax.lang.model.element.Modifier;
7
8public final class DropVarModifiersAstTransform implements IAstTransform {
9 public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform();
10
11 private DropVarModifiersAstTransform() {
12 }
13
14 @Override
15 public void run(AstNode compilationUnit) {
16 compilationUnit.acceptVisitor(new Visitor(), null);
17 }
18
19 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
20 @Override
21 public Void visitParameterDeclaration(ParameterDeclaration node, Void data) {
22 for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) {
23 if (modifierToken.getModifier() == Modifier.FINAL) {
24 modifierToken.remove();
25 }
26 }
27
28 return null;
29 }
30
31 @Override
32 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) {
33 node.removeModifier(Modifier.FINAL);
34 return null;
35 }
36 }
37}
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 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.AstNode;
4import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
5import com.strobel.decompiler.languages.java.ast.Identifier;
6import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
7
8/**
9 * Created by Thiakil on 13/07/2018.
10 */
11public class InvalidIdentifierFix implements IAstTransform {
12 @Override
13 public void run(AstNode compilationUnit) {
14 compilationUnit.acceptVisitor(new Visitor(), null);
15 }
16
17 class Visitor extends DepthFirstAstVisitor<Void,Void>{
18 @Override
19 public Void visitIdentifier(Identifier node, Void data) {
20 super.visitIdentifier(node, data);
21 if (node.getName().equals("do") || node.getName().equals("if")){
22 Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation());
23 newIdentifier.copyUserDataFrom(node);
24 node.replaceWith(newIdentifier);
25 }
26 return null;
27 }
28 }
29}
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 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.BuiltinTypes;
4import com.strobel.assembler.metadata.CommonTypeReferences;
5import com.strobel.assembler.metadata.Flags;
6import com.strobel.assembler.metadata.IGenericInstance;
7import com.strobel.assembler.metadata.IMemberDefinition;
8import com.strobel.assembler.metadata.JvmType;
9import com.strobel.assembler.metadata.MemberReference;
10import com.strobel.assembler.metadata.MethodDefinition;
11import com.strobel.assembler.metadata.TypeDefinition;
12import com.strobel.assembler.metadata.TypeReference;
13import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
14import com.strobel.decompiler.languages.java.ast.AstNode;
15import com.strobel.decompiler.languages.java.ast.AstNodeCollection;
16import com.strobel.decompiler.languages.java.ast.AstType;
17import com.strobel.decompiler.languages.java.ast.CastExpression;
18import com.strobel.decompiler.languages.java.ast.ComposedType;
19import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
20import com.strobel.decompiler.languages.java.ast.Expression;
21import com.strobel.decompiler.languages.java.ast.Identifier;
22import com.strobel.decompiler.languages.java.ast.InvocationExpression;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
26import com.strobel.decompiler.languages.java.ast.Roles;
27import com.strobel.decompiler.languages.java.ast.SimpleType;
28import com.strobel.decompiler.languages.java.ast.WildcardType;
29import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
30
31/**
32 * Created by Thiakil on 12/07/2018.
33 */
34public class Java8Generics implements IAstTransform {
35
36 @Override
37 public void run(AstNode compilationUnit) {
38 compilationUnit.acceptVisitor(new Visitor(), null);
39 }
40
41 static class Visitor extends DepthFirstAstVisitor<Void,Void>{
42
43 @Override
44 public Void visitInvocationExpression(InvocationExpression node, Void data) {
45 super.visitInvocationExpression(node, data);
46 if (node.getTarget() instanceof MemberReferenceExpression){
47 MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget();
48 if (referenceExpression.getTypeArguments().stream().map(t->{
49 TypeReference tr = t.toTypeReference();
50 if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below
51 TypeReference resolved = tr.resolve();
52 if (resolved != null)
53 return resolved;
54 }
55 return tr;
56 }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) {
57 //these are invalid for invocations, let the compiler work it out
58 referenceExpression.getTypeArguments().clear();
59 } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){
60 //all are <Object>, thereby redundant and/or bad
61 referenceExpression.getTypeArguments().clear();
62 }
63 }
64 return null;
65 }
66
67 @Override
68 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) {
69 super.visitObjectCreationExpression(node, data);
70 AstType type = node.getType();
71 if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){
72 SimpleType simpleType = (SimpleType) type;
73 AstNodeCollection<AstType> typeArguments = simpleType.getTypeArguments();
74 if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){
75 //all are <Object>, thereby redundant and/or bad
76 typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create(""));
77 }
78 }
79 return null;
80 }
81
82 @Override
83 public Void visitCastExpression(CastExpression node, Void data) {
84 boolean doReplace = false;
85 TypeReference typeReference = node.getType().toTypeReference();
86 if (typeReference.isArray() && typeReference.getElementType().isGenericType()){
87 doReplace = true;
88 } else if (typeReference.isGenericType()) {
89 Expression target = node.getExpression();
90 if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){
91 doReplace = true;
92 } else if (target instanceof InvocationExpression) {
93 InvocationExpression invocationExpression = (InvocationExpression)target;
94 if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) {
95 ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear();
96 doReplace = true;
97 }
98 }
99 }
100 super.visitCastExpression(node, data);
101 if (doReplace){
102 node.replaceWith(node.getExpression());
103 }
104 return null;
105 }
106 }
107}
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 @@
1/*
2 * Originally:
3 * EnumSwitchRewriterTransform.java
4 *
5 * Copyright (c) 2013 Mike Strobel
6 *
7 * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
8 * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
9 *
10 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
11 * A copy of the license can be found in the License.html file at the root of this distribution.
12 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
13 * Apache License, Version 2.0.
14 *
15 * You must not remove this notice, or any other, from this software.
16 */
17
18package cuchaz.enigma.source.procyon.transformers;
19
20import com.strobel.assembler.metadata.BuiltinTypes;
21import com.strobel.assembler.metadata.FieldDefinition;
22import com.strobel.assembler.metadata.MethodDefinition;
23import com.strobel.assembler.metadata.TypeDefinition;
24import com.strobel.assembler.metadata.TypeReference;
25import com.strobel.core.SafeCloseable;
26import com.strobel.core.VerifyArgument;
27import com.strobel.decompiler.DecompilerContext;
28import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
29import com.strobel.decompiler.languages.java.ast.AstBuilder;
30import com.strobel.decompiler.languages.java.ast.AstNode;
31import com.strobel.decompiler.languages.java.ast.CaseLabel;
32import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
33import com.strobel.decompiler.languages.java.ast.Expression;
34import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
35import com.strobel.decompiler.languages.java.ast.IndexerExpression;
36import com.strobel.decompiler.languages.java.ast.InvocationExpression;
37import com.strobel.decompiler.languages.java.ast.Keys;
38import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
39import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
40import com.strobel.decompiler.languages.java.ast.SwitchSection;
41import com.strobel.decompiler.languages.java.ast.SwitchStatement;
42import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
43import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
44import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
45
46import java.util.ArrayList;
47import java.util.IdentityHashMap;
48import java.util.LinkedHashMap;
49import java.util.List;
50import java.util.Map;
51
52/**
53 * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to:
54 * - Not rely on a field containing "$SwitchMap$" (Proguard strips it)
55 * - Ignore classes *with* SwitchMap$ names (so the original can handle it)
56 * - Ignores inner synthetics that are not package private
57 */
58@SuppressWarnings("Duplicates")
59public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform {
60 private final DecompilerContext _context;
61
62 public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) {
63 _context = VerifyArgument.notNull(context, "context");
64 }
65
66 @Override
67 public void run(final AstNode compilationUnit) {
68 compilationUnit.acceptVisitor(new Visitor(_context), null);
69 }
70
71 private final static class Visitor extends ContextTrackingVisitor<Void> {
72 private final static class SwitchMapInfo {
73 final String enclosingType;
74 final Map<String, List<SwitchStatement>> switches = new LinkedHashMap<>();
75 final Map<String, Map<Integer, Expression>> mappings = new LinkedHashMap<>();
76
77 TypeDeclaration enclosingTypeDeclaration;
78
79 SwitchMapInfo(final String enclosingType) {
80 this.enclosingType = enclosingType;
81 }
82 }
83
84 private final Map<String, SwitchMapInfo> _switchMaps = new LinkedHashMap<>();
85 private boolean _isSwitchMapWrapper;
86
87 protected Visitor(final DecompilerContext context) {
88 super(context);
89 }
90
91 @Override
92 public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) {
93 final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper;
94 final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION);
95 final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition);
96
97 if (isSwitchMapWrapper) {
98 final String internalName = typeDefinition.getInternalName();
99
100 SwitchMapInfo info = _switchMaps.get(internalName);
101
102 if (info == null) {
103 _switchMaps.put(internalName, info = new SwitchMapInfo(internalName));
104 }
105
106 info.enclosingTypeDeclaration = typeDeclaration;
107 }
108
109 _isSwitchMapWrapper = isSwitchMapWrapper;
110
111 try {
112 super.visitTypeDeclaration(typeDeclaration, p);
113 }
114 finally {
115 _isSwitchMapWrapper = oldIsSwitchMapWrapper;
116 }
117
118 rewrite();
119
120 return null;
121 }
122
123 @Override
124 public Void visitSwitchStatement(final SwitchStatement node, final Void data) {
125 final Expression test = node.getExpression();
126
127 if (test instanceof IndexerExpression) {
128 final IndexerExpression indexer = (IndexerExpression) test;
129 final Expression array = indexer.getTarget();
130 final Expression argument = indexer.getArgument();
131
132 if (!(array instanceof MemberReferenceExpression)) {
133 return super.visitSwitchStatement(node, data);
134 }
135
136 final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array;
137 final Expression arrayOwner = arrayAccess.getTarget();
138 final String mapName = arrayAccess.getMemberName();
139
140 if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) {
141 return super.visitSwitchStatement(node, data);
142 }
143
144 final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner;
145 final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE);
146
147 if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) {
148 return super.visitSwitchStatement(node, data);
149 }
150
151 final InvocationExpression invocation = (InvocationExpression) argument;
152 final Expression invocationTarget = invocation.getTarget();
153
154 if (!(invocationTarget instanceof MemberReferenceExpression)) {
155 return super.visitSwitchStatement(node, data);
156 }
157
158 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
159
160 if (!"ordinal".equals(memberReference.getMemberName())) {
161 return super.visitSwitchStatement(node, data);
162 }
163
164 final String enclosingTypeName = enclosingType.getInternalName();
165
166 SwitchMapInfo info = _switchMaps.get(enclosingTypeName);
167
168 if (info == null) {
169 _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName));
170
171 final TypeDefinition resolvedType = enclosingType.resolve();
172
173 if (resolvedType != null) {
174 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
175
176 if (astBuilder == null) {
177 astBuilder = new AstBuilder(context);
178 }
179
180 try (final SafeCloseable importSuppression = astBuilder.suppressImports()) {
181 final TypeDeclaration declaration = astBuilder.createType(resolvedType);
182
183 declaration.acceptVisitor(this, data);
184 }
185 }
186 }
187
188 List<SwitchStatement> switches = info.switches.get(mapName);
189
190 if (switches == null) {
191 info.switches.put(mapName, switches = new ArrayList<>());
192 }
193
194 switches.add(node);
195 }
196
197 return super.visitSwitchStatement(node, data);
198 }
199
200 @Override
201 public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) {
202 final TypeDefinition currentType = context.getCurrentType();
203 final MethodDefinition currentMethod = context.getCurrentMethod();
204
205 if (_isSwitchMapWrapper &&
206 currentType != null &&
207 currentMethod != null &&
208 currentMethod.isTypeInitializer()) {
209
210 final Expression left = node.getLeft();
211 final Expression right = node.getRight();
212
213 if (left instanceof IndexerExpression &&
214 right instanceof PrimitiveExpression) {
215
216 String mapName = null;
217
218 final Expression array = ((IndexerExpression) left).getTarget();
219 final Expression argument = ((IndexerExpression) left).getArgument();
220
221 if (array instanceof MemberReferenceExpression) {
222 mapName = ((MemberReferenceExpression) array).getMemberName();
223 }
224 else if (array instanceof IdentifierExpression) {
225 mapName = ((IdentifierExpression) array).getIdentifier();
226 }
227
228 if (mapName == null || mapName.startsWith("$SwitchMap$")) {
229 return super.visitAssignmentExpression(node, data);
230 }
231
232 if (!(argument instanceof InvocationExpression)) {
233 return super.visitAssignmentExpression(node, data);
234 }
235
236 final InvocationExpression invocation = (InvocationExpression) argument;
237 final Expression invocationTarget = invocation.getTarget();
238
239 if (!(invocationTarget instanceof MemberReferenceExpression)) {
240 return super.visitAssignmentExpression(node, data);
241 }
242
243 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
244 final Expression memberTarget = memberReference.getTarget();
245
246 if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) {
247 return super.visitAssignmentExpression(node, data);
248 }
249
250 final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget;
251 final Expression outerMemberTarget = outerMemberReference.getTarget();
252
253 if (!(outerMemberTarget instanceof TypeReferenceExpression)) {
254 return super.visitAssignmentExpression(node, data);
255 }
256
257 final String enclosingType = currentType.getInternalName();
258
259 SwitchMapInfo info = _switchMaps.get(enclosingType);
260
261 if (info == null) {
262 _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType));
263
264 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
265
266 if (astBuilder == null) {
267 astBuilder = new AstBuilder(context);
268 }
269
270 info.enclosingTypeDeclaration = astBuilder.createType(currentType);
271 }
272
273 final PrimitiveExpression value = (PrimitiveExpression) right;
274
275 assert value.getValue() instanceof Integer;
276
277 Map<Integer, Expression> mapping = info.mappings.get(mapName);
278
279 if (mapping == null) {
280 info.mappings.put(mapName, mapping = new LinkedHashMap<>());
281 }
282
283 final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName());
284
285 enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE));
286
287 mapping.put(((Number) value.getValue()).intValue(), enumValue);
288 }
289 }
290
291 return super.visitAssignmentExpression(node, data);
292 }
293
294 private void rewrite() {
295 if (_switchMaps.isEmpty()) {
296 return;
297 }
298
299 for (final SwitchMapInfo info : _switchMaps.values()) {
300 rewrite(info);
301 }
302
303 //
304 // Remove switch map type wrappers that are no longer referenced.
305 //
306
307 outer:
308 for (final SwitchMapInfo info : _switchMaps.values()) {
309 for (final String mapName : info.switches.keySet()) {
310 final List<SwitchStatement> switches = info.switches.get(mapName);
311
312 if (switches != null && !switches.isEmpty()) {
313 continue outer;
314 }
315 }
316
317 final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration;
318
319 if (enclosingTypeDeclaration != null) {
320 enclosingTypeDeclaration.remove();
321 }
322 }
323 }
324
325 private void rewrite(final SwitchMapInfo info) {
326 if (info.switches.isEmpty()) {
327 return;
328 }
329
330 for (final String mapName : info.switches.keySet()) {
331 final List<SwitchStatement> switches = info.switches.get(mapName);
332 final Map<Integer, Expression> mappings = info.mappings.get(mapName);
333
334 if (switches != null && mappings != null) {
335 for (int i = 0; i < switches.size(); i++) {
336 if (rewriteSwitch(switches.get(i), mappings)) {
337 switches.remove(i--);
338 }
339 }
340 }
341 }
342 }
343
344 private boolean rewriteSwitch(final SwitchStatement s, final Map<Integer, Expression> mappings) {
345 final Map<Expression, Expression> replacements = new IdentityHashMap<>();
346
347 for (final SwitchSection section : s.getSwitchSections()) {
348 for (final CaseLabel caseLabel : section.getCaseLabels()) {
349 final Expression expression = caseLabel.getExpression();
350
351 if (expression.isNull()) {
352 continue;
353 }
354
355 if (expression instanceof PrimitiveExpression) {
356 final Object value = ((PrimitiveExpression) expression).getValue();
357
358 if (value instanceof Integer) {
359 final Expression replacement = mappings.get(value);
360
361 if (replacement != null) {
362 replacements.put(expression, replacement);
363 continue;
364 }
365 }
366 }
367
368 //
369 // If we can't rewrite all cases, we abort.
370 //
371
372 return false;
373 }
374 }
375
376 final IndexerExpression indexer = (IndexerExpression) s.getExpression();
377 final InvocationExpression argument = (InvocationExpression) indexer.getArgument();
378 final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget();
379 final Expression newTest = memberReference.getTarget();
380
381 newTest.remove();
382 indexer.replaceWith(newTest);
383
384 for (final Map.Entry<Expression, Expression> entry : replacements.entrySet()) {
385 entry.getKey().replaceWith(entry.getValue().clone());
386 }
387
388 return true;
389 }
390
391 private static boolean isSwitchMapWrapper(final TypeReference type) {
392 if (type == null) {
393 return false;
394 }
395
396 final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type
397 : type.resolve();
398
399 if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) {
400 return false;
401 }
402
403 for (final FieldDefinition field : definition.getDeclaredFields()) {
404 if (!field.getName().startsWith("$SwitchMap$") &&
405 BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) {
406
407 return true;
408 }
409 }
410
411 return false;
412 }
413 }
414} \ 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 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.BuiltinTypes;
4import com.strobel.decompiler.DecompilerContext;
5import com.strobel.decompiler.languages.java.ast.AstNode;
6import com.strobel.decompiler.languages.java.ast.CastExpression;
7import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
8import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
9
10/**
11 * Created by Thiakil on 11/07/2018.
12 */
13public class RemoveObjectCasts implements IAstTransform {
14 private final DecompilerContext _context;
15
16 public RemoveObjectCasts(DecompilerContext context) {
17 _context = context;
18 }
19
20 @Override
21 public void run(AstNode compilationUnit) {
22 compilationUnit.acceptVisitor(new Visitor(_context), null);
23 }
24
25 private final static class Visitor extends ContextTrackingVisitor<Void>{
26
27 protected Visitor(DecompilerContext context) {
28 super(context);
29 }
30
31 @Override
32 public Void visitCastExpression(CastExpression node, Void data) {
33 if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){
34 node.replaceWith(node.getExpression());
35 }
36 return super.visitCastExpression(node, data);
37 }
38 }
39}
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 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.MemberReference;
4import com.strobel.assembler.metadata.MetadataFilters;
5import com.strobel.assembler.metadata.MetadataHelper;
6import com.strobel.assembler.metadata.MethodBinder;
7import com.strobel.assembler.metadata.MethodDefinition;
8import com.strobel.assembler.metadata.MethodReference;
9import com.strobel.assembler.metadata.TypeReference;
10import com.strobel.core.StringUtilities;
11import com.strobel.core.VerifyArgument;
12import com.strobel.decompiler.DecompilerContext;
13import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
14import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
15import com.strobel.decompiler.languages.java.ast.AstNode;
16import com.strobel.decompiler.languages.java.ast.AstNodeCollection;
17import com.strobel.decompiler.languages.java.ast.CastExpression;
18import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
19import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
20import com.strobel.decompiler.languages.java.ast.Expression;
21import com.strobel.decompiler.languages.java.ast.InvocationExpression;
22import com.strobel.decompiler.languages.java.ast.JavaResolver;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
26import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
27import com.strobel.decompiler.semantics.ResolveResult;
28
29import java.util.ArrayList;
30import java.util.List;
31
32/**
33 * Created by Thiakil on 12/07/2018.
34 */
35public class VarargsFixer implements IAstTransform {
36 private final DecompilerContext _context;
37
38 public VarargsFixer(final DecompilerContext context) {
39 _context = VerifyArgument.notNull(context, "context");
40 }
41
42 @Override
43 public void run(AstNode compilationUnit) {
44 compilationUnit.acceptVisitor(new Visitor(_context), null);
45 }
46
47 class Visitor extends ContextTrackingVisitor<Void> {
48 private final JavaResolver _resolver;
49 protected Visitor(DecompilerContext context) {
50 super(context);
51 _resolver = new JavaResolver(context);
52 }
53
54 //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them
55 @Override
56 public Void visitInvocationExpression(InvocationExpression node, Void data) {
57 super.visitInvocationExpression(node, data);
58 MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE);
59 if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){
60 AstNodeCollection<Expression> arguments = node.getArguments();
61 Expression lastParam = arguments.lastOrNullObject();
62 if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){
63 ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam;
64 if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){
65 lastParam.remove();
66 } else {
67 for (Expression e : varargArray.getInitializer().getElements()){
68 arguments.insertBefore(varargArray, e.clone());
69 }
70 varargArray.remove();
71 }
72 }
73 }
74 return null;
75 }
76
77 //applies the vararg transform to object creation
78 @Override
79 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) {
80 super.visitObjectCreationExpression(node, data);
81 final AstNodeCollection<Expression> arguments = node.getArguments();
82 final Expression lastArgument = arguments.lastOrNullObject();
83
84 Expression arrayArg = lastArgument;
85
86 if (arrayArg instanceof CastExpression)
87 arrayArg = ((CastExpression) arrayArg).getExpression();
88
89 if (arrayArg == null ||
90 arrayArg.isNull() ||
91 !(arrayArg instanceof ArrayCreationExpression &&
92 node.getTarget() instanceof MemberReferenceExpression)) {
93
94 return null;
95 }
96
97 final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg;
98 final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget();
99
100 if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) {
101 return null;
102 }
103
104 final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE);
105
106 if (method == null) {
107 return null;
108 }
109
110 final MethodDefinition resolved = method.resolve();
111
112 if (resolved == null || !resolved.isVarArgs()) {
113 return null;
114 }
115
116 final List<MethodReference> candidates;
117 final Expression invocationTarget = target.getTarget();
118
119 if (invocationTarget == null || invocationTarget.isNull()) {
120 candidates = MetadataHelper.findMethods(
121 context.getCurrentType(),
122 MetadataFilters.matchName(resolved.getName())
123 );
124 }
125 else {
126 final ResolveResult targetResult = _resolver.apply(invocationTarget);
127
128 if (targetResult == null || targetResult.getType() == null) {
129 return null;
130 }
131
132 candidates = MetadataHelper.findMethods(
133 targetResult.getType(),
134 MetadataFilters.matchName(resolved.getName())
135 );
136 }
137
138 final List<TypeReference> argTypes = new ArrayList<>();
139
140 for (final Expression argument : arguments) {
141 final ResolveResult argResult = _resolver.apply(argument);
142
143 if (argResult == null || argResult.getType() == null) {
144 return null;
145 }
146
147 argTypes.add(argResult.getType());
148 }
149
150 final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes);
151
152 if (c1.isFailure() || c1.isAmbiguous()) {
153 return null;
154 }
155
156 argTypes.remove(argTypes.size() - 1);
157
158 final ArrayInitializerExpression initializer = newArray.getInitializer();
159 final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty();
160
161 if (hasElements) {
162 for (final Expression argument : initializer.getElements()) {
163 final ResolveResult argResult = _resolver.apply(argument);
164
165 if (argResult == null || argResult.getType() == null) {
166 return null;
167 }
168
169 argTypes.add(argResult.getType());
170 }
171 }
172
173 final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes);
174
175 if (c2.isFailure() ||
176 c2.isAmbiguous() ||
177 !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) {
178
179 return null;
180 }
181
182 lastArgument.remove();
183
184 if (!hasElements) {
185 lastArgument.remove();
186 return null;
187 }
188
189 for (final Expression newArg : initializer.getElements()) {
190 newArg.remove();
191 arguments.add(newArg);
192 }
193
194 return null;
195 }
196 }
197}
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 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.Buffer;
4import com.strobel.assembler.metadata.ClasspathTypeLoader;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7/**
8 * Caching version of {@link ClasspathTypeLoader}
9 */
10public class CachingClasspathTypeLoader extends CachingTypeLoader {
11 private static ITypeLoader extraClassPathLoader = null;
12
13 public static void setExtraClassPathLoader(ITypeLoader loader){
14 extraClassPathLoader = loader;
15 }
16
17 private final ITypeLoader classpathLoader = new ClasspathTypeLoader();
18
19 @Override
20 protected byte[] doLoad(String className) {
21 Buffer parentBuf = new Buffer();
22 if (classpathLoader.tryLoadType(className, parentBuf)) {
23 return parentBuf.array();
24 }
25 if (extraClassPathLoader != null){
26 parentBuf.reset();
27 if (extraClassPathLoader.tryLoadType(className, parentBuf)){
28 return parentBuf.array();
29 }
30 }
31 return EMPTY_ARRAY;//need to return *something* as null means no store
32 }
33}
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 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.google.common.collect.Maps;
4import com.strobel.assembler.metadata.Buffer;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7import java.util.Map;
8
9/**
10 * Common cache functions
11 */
12public abstract class CachingTypeLoader implements ITypeLoader {
13 protected static final byte[] EMPTY_ARRAY = {};
14
15 private final Map<String, byte[]> cache = Maps.newHashMap();
16
17 protected abstract byte[] doLoad(String className);
18
19 @Override
20 public boolean tryLoadType(String className, Buffer out) {
21
22 // check the cache
23 byte[] data = this.cache.computeIfAbsent(className, this::doLoad);
24
25 if (data == EMPTY_ARRAY) {
26 return false;
27 }
28
29 out.reset(data.length);
30 System.arraycopy(data, 0, out.array(), out.position(), data.length);
31 out.position(0);
32 return true;
33 }
34
35 public void clearCache() {
36 this.cache.clear();
37 }
38}
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 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.typeloader;
13
14import com.google.common.collect.Lists;
15import com.strobel.assembler.metadata.Buffer;
16import com.strobel.assembler.metadata.ITypeLoader;
17import cuchaz.enigma.ClassProvider;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import org.objectweb.asm.ClassVisitor;
20import org.objectweb.asm.ClassWriter;
21import org.objectweb.asm.Opcodes;
22import org.objectweb.asm.tree.AbstractInsnNode;
23import org.objectweb.asm.tree.ClassNode;
24import org.objectweb.asm.tree.MethodInsnNode;
25import org.objectweb.asm.tree.MethodNode;
26
27import java.util.Collection;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.function.Function;
31
32public class CompiledSourceTypeLoader extends CachingTypeLoader {
33 //Store one instance as the classpath shouldn't change during load
34 private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader();
35
36 private final ClassProvider compiledSource;
37 private final LinkedList<Function<ClassVisitor, ClassVisitor>> visitors = new LinkedList<>();
38
39 public CompiledSourceTypeLoader(ClassProvider compiledSource) {
40 this.compiledSource = compiledSource;
41 }
42
43 public void addVisitor(Function<ClassVisitor, ClassVisitor> visitor) {
44 this.visitors.addFirst(visitor);
45 }
46
47 @Override
48 protected byte[] doLoad(String className) {
49 byte[] data = loadType(className);
50 if (data == null) {
51 return loadClasspath(className);
52 }
53
54 return data;
55 }
56
57 private byte[] loadClasspath(String name) {
58 Buffer parentBuf = new Buffer();
59 if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) {
60 return parentBuf.array();
61 }
62 return EMPTY_ARRAY;
63 }
64
65 private byte[] loadType(String className) {
66 ClassEntry entry = new ClassEntry(className);
67
68 // find the class in the jar
69 ClassNode node = findClassNode(entry);
70 if (node == null) {
71 // couldn't find it
72 return null;
73 }
74
75 removeRedundantClassCalls(node);
76
77 ClassWriter writer = new ClassWriter(0);
78
79 ClassVisitor visitor = writer;
80 for (Function<ClassVisitor, ClassVisitor> visitorFunction : this.visitors) {
81 visitor = visitorFunction.apply(visitor);
82 }
83
84 node.accept(visitor);
85
86 // we have a transformed class!
87 return writer.toByteArray();
88 }
89
90 private void removeRedundantClassCalls(ClassNode node) {
91 // remove <obj>.getClass() calls that are seemingly injected
92 // DUP
93 // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
94 // POP
95 for (MethodNode methodNode : node.methods) {
96 AbstractInsnNode insnNode = methodNode.instructions.getFirst();
97 while (insnNode != null) {
98 if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
99 MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
100 if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) {
101 AbstractInsnNode previous = methodInsnNode.getPrevious();
102 AbstractInsnNode next = methodInsnNode.getNext();
103 if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) {
104 insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction
105 methodNode.instructions.remove(previous);
106 methodNode.instructions.remove(methodInsnNode);
107 methodNode.instructions.remove(next);
108 }
109 }
110 }
111 insnNode = insnNode.getNext();
112 }
113 }
114 }
115
116 private ClassNode findClassNode(ClassEntry entry) {
117 // try to find the class in the jar
118 for (String className : getClassNamesToTry(entry)) {
119 ClassNode node = compiledSource.getClassNode(className);
120 if (node != null) {
121 return node;
122 }
123 }
124
125 // didn't find it ;_;
126 return null;
127 }
128
129 private Collection<String> getClassNamesToTry(ClassEntry entry) {
130 List<String> classNamesToTry = Lists.newArrayList();
131 classNamesToTry.add(entry.getFullName());
132
133 ClassEntry outerClass = entry.getOuterClass();
134 if (outerClass != null) {
135 classNamesToTry.addAll(getClassNamesToTry(outerClass));
136 }
137
138 return classNamesToTry;
139 }
140}
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 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import com.strobel.assembler.metadata.MetadataSystem;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7
8import java.util.Collections;
9import java.util.Set;
10import java.util.concurrent.ConcurrentHashMap;
11
12public final class NoRetryMetadataSystem extends MetadataSystem {
13 private final Set<String> failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>());
14
15 public NoRetryMetadataSystem(final ITypeLoader typeLoader) {
16 super(typeLoader);
17 }
18
19 @Override
20 protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) {
21 if (failedTypes.contains(descriptor)) {
22 return null;
23 }
24
25 final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive);
26
27 if (result == null) {
28 failedTypes.add(descriptor);
29 }
30
31 return result;
32 }
33
34 @Override
35 public synchronized TypeDefinition resolve(final TypeReference type) {
36 return super.resolve(type);
37 }
38}
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 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.Buffer;
4import com.strobel.assembler.metadata.ITypeLoader;
5
6/**
7 * Typeloader with synchronized tryLoadType method
8 */
9public class SynchronizedTypeLoader implements ITypeLoader {
10 private final ITypeLoader delegate;
11
12 public SynchronizedTypeLoader(ITypeLoader delegate) {
13 this.delegate = delegate;
14 }
15
16 @Override
17 public synchronized boolean tryLoadType(String internalName, Buffer buffer) {
18 return delegate.tryLoadType(internalName, buffer);
19 }
20}