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--- src/main/java/cuchaz/enigma/source/Decompiler.java | 5 + .../cuchaz/enigma/source/DecompilerService.java | 11 + .../java/cuchaz/enigma/source/Decompilers.java | 9 + src/main/java/cuchaz/enigma/source/Source.java | 11 + .../java/cuchaz/enigma/source/SourceIndex.java | 174 +++++++++ .../java/cuchaz/enigma/source/SourceSettings.java | 11 + .../cuchaz/enigma/source/cfr/CfrDecompiler.java | 108 ++++++ .../java/cuchaz/enigma/source/cfr/CfrSource.java | 38 ++ .../cuchaz/enigma/source/cfr/EnigmaDumper.java | 427 +++++++++++++++++++++ .../cuchaz/enigma/source/procyon/EntryParser.java | 49 +++ .../enigma/source/procyon/ProcyonDecompiler.java | 81 ++++ .../enigma/source/procyon/ProcyonSource.java | 49 +++ .../procyon/index/SourceIndexClassVisitor.java | 95 +++++ .../procyon/index/SourceIndexMethodVisitor.java | 218 +++++++++++ .../source/procyon/index/SourceIndexVisitor.java | 40 ++ .../enigma/source/procyon/index/TokenFactory.java | 41 ++ .../transformers/AddJavadocsAstTransform.java | 134 +++++++ .../transformers/DropImportAstTransform.java | 33 ++ .../transformers/DropVarModifiersAstTransform.java | 37 ++ .../procyon/transformers/InvalidIdentifierFix.java | 29 ++ .../source/procyon/transformers/Java8Generics.java | 107 ++++++ .../ObfuscatedEnumSwitchRewriterTransform.java | 414 ++++++++++++++++++++ .../procyon/transformers/RemoveObjectCasts.java | 39 ++ .../source/procyon/transformers/VarargsFixer.java | 197 ++++++++++ .../typeloader/CachingClasspathTypeLoader.java | 33 ++ .../procyon/typeloader/CachingTypeLoader.java | 38 ++ .../typeloader/CompiledSourceTypeLoader.java | 140 +++++++ .../procyon/typeloader/NoRetryMetadataSystem.java | 38 ++ .../procyon/typeloader/SynchronizedTypeLoader.java | 20 + 29 files changed, 2626 insertions(+) create mode 100644 src/main/java/cuchaz/enigma/source/Decompiler.java create mode 100644 src/main/java/cuchaz/enigma/source/DecompilerService.java create mode 100644 src/main/java/cuchaz/enigma/source/Decompilers.java create mode 100644 src/main/java/cuchaz/enigma/source/Source.java create mode 100644 src/main/java/cuchaz/enigma/source/SourceIndex.java create mode 100644 src/main/java/cuchaz/enigma/source/SourceSettings.java create mode 100644 src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java create mode 100644 src/main/java/cuchaz/enigma/source/cfr/CfrSource.java create mode 100644 src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/EntryParser.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java create mode 100644 src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java (limited to 'src/main/java/cuchaz/enigma/source') diff --git a/src/main/java/cuchaz/enigma/source/Decompiler.java b/src/main/java/cuchaz/enigma/source/Decompiler.java new file mode 100644 index 0000000..c9666d5 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Decompiler.java @@ -0,0 +1,5 @@ +package cuchaz.enigma.source; + +public interface Decompiler { + Source getSource(String className); +} diff --git a/src/main/java/cuchaz/enigma/source/DecompilerService.java b/src/main/java/cuchaz/enigma/source/DecompilerService.java new file mode 100644 index 0000000..377ccbc --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/DecompilerService.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.api.service.EnigmaService; +import cuchaz.enigma.api.service.EnigmaServiceType; + +public interface DecompilerService extends EnigmaService { + EnigmaServiceType TYPE = EnigmaServiceType.create("decompiler"); + + Decompiler create(ClassProvider classProvider, SourceSettings settings); +} diff --git a/src/main/java/cuchaz/enigma/source/Decompilers.java b/src/main/java/cuchaz/enigma/source/Decompilers.java new file mode 100644 index 0000000..7d154a6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Decompilers.java @@ -0,0 +1,9 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.source.cfr.CfrDecompiler; +import cuchaz.enigma.source.procyon.ProcyonDecompiler; + +public class Decompilers { + public static final DecompilerService PROCYON = ProcyonDecompiler::new; + public static final DecompilerService CFR = CfrDecompiler::new; +} diff --git a/src/main/java/cuchaz/enigma/source/Source.java b/src/main/java/cuchaz/enigma/source/Source.java new file mode 100644 index 0000000..43c4de0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Source.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +import cuchaz.enigma.translation.mapping.EntryRemapper; + +public interface Source { + String asString(); + + Source addJavadocs(EntryRemapper remapper); + + SourceIndex index(); +} diff --git a/src/main/java/cuchaz/enigma/source/SourceIndex.java b/src/main/java/cuchaz/enigma/source/SourceIndex.java new file mode 100644 index 0000000..6a335ec --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/SourceIndex.java @@ -0,0 +1,174 @@ +package cuchaz.enigma.source; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.gui.SourceRemapper; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.Entry; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public class SourceIndex { + private String source; + private List lineOffsets; + private final TreeMap, Entry>> tokenToReference; + private final Multimap, Entry>, Token> referenceToTokens; + private final Map, Token> declarationToToken; + + public SourceIndex() { + tokenToReference = new TreeMap<>(); + referenceToTokens = HashMultimap.create(); + declarationToToken = Maps.newHashMap(); + } + + public SourceIndex(String source) { + this(); + setSource(source); + } + + public void setSource(String source) { + this.source = source; + lineOffsets = Lists.newArrayList(); + lineOffsets.add(0); + + for (int i = 0; i < this.source.length(); i++) { + if (this.source.charAt(i) == '\n') { + lineOffsets.add(i + 1); + } + } + } + + public String getSource() { + return source; + } + + public int getLineNumber(int position) { + int line = 0; + + for (int offset : lineOffsets) { + if (offset > position) { + break; + } + + line++; + } + + return line; + } + + public int getColumnNumber(int position) { + return position - lineOffsets.get(getLineNumber(position) - 1) + 1; + } + + public int getPosition(int line, int column) { + return lineOffsets.get(line - 1) + column - 1; + } + + public Iterable> declarations() { + return declarationToToken.keySet(); + } + + public Iterable declarationTokens() { + return declarationToToken.values(); + } + + public Token getDeclarationToken(Entry entry) { + return declarationToToken.get(entry); + } + + public void addDeclaration(Token token, Entry deobfEntry) { + if (token != null) { + EntryReference, Entry> reference = new EntryReference<>(deobfEntry, token.text); + tokenToReference.put(token, reference); + referenceToTokens.put(reference, token); + declarationToToken.put(deobfEntry, token); + } + } + + public Iterable, Entry>> references() { + return referenceToTokens.keySet(); + } + + public EntryReference, Entry> getReference(Token token) { + if (token == null) { + return null; + } + + return tokenToReference.get(token); + } + + public Iterable referenceTokens() { + return tokenToReference.keySet(); + } + + public Token getReferenceToken(int pos) { + Token token = tokenToReference.floorKey(new Token(pos, pos, null)); + + if (token != null && token.contains(pos)) { + return token; + } + + return null; + } + + public Collection getReferenceTokens(EntryReference, Entry> deobfReference) { + return referenceToTokens.get(deobfReference); + } + + public void addReference(Token token, Entry deobfEntry, Entry deobfContext) { + if (token != null) { + EntryReference, Entry> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); + tokenToReference.put(token, deobfReference); + referenceToTokens.put(deobfReference, token); + } + } + + public void resolveReferences(EntryResolver resolver) { + // resolve all the classes in the source references + for (Token token : Lists.newArrayList(referenceToTokens.values())) { + EntryReference, Entry> reference = tokenToReference.get(token); + EntryReference, Entry> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); + + // replace the reference + tokenToReference.replace(token, resolvedReference); + + Collection tokens = referenceToTokens.removeAll(reference); + referenceToTokens.putAll(resolvedReference, tokens); + } + } + + public SourceIndex remapTo(SourceRemapper.Result result) { + SourceIndex remapped = new SourceIndex(result.getSource()); + + for (Map.Entry, Token> entry : declarationToToken.entrySet()) { + remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); + } + + for (Map.Entry, Entry>, Collection> entry : referenceToTokens.asMap().entrySet()) { + EntryReference, Entry> reference = entry.getKey(); + Collection oldTokens = entry.getValue(); + + Collection newTokens = oldTokens + .stream() + .map(result::getRemappedToken) + .collect(Collectors.toList()); + + remapped.referenceToTokens.putAll(reference, newTokens); + } + + for (Map.Entry, Entry>> entry : tokenToReference.entrySet()) { + remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue()); + } + + return remapped; + } +} diff --git a/src/main/java/cuchaz/enigma/source/SourceSettings.java b/src/main/java/cuchaz/enigma/source/SourceSettings.java new file mode 100644 index 0000000..f6c68e9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/SourceSettings.java @@ -0,0 +1,11 @@ +package cuchaz.enigma.source; + +public class SourceSettings { + public final boolean removeImports; + public final boolean removeVariableFinal; + + public SourceSettings(boolean removeImports, boolean removeVariableFinal) { + this.removeImports = removeImports; + this.removeVariableFinal = removeVariableFinal; + } +} diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java new file mode 100644 index 0000000..9e37f16 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java @@ -0,0 +1,108 @@ +package cuchaz.enigma.source.cfr; + +import com.google.common.io.ByteStreams; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceSettings; +import org.benf.cfr.reader.apiunreleased.ClassFileSource2; +import org.benf.cfr.reader.apiunreleased.JarContent; +import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.mapping.MappingFactory; +import org.benf.cfr.reader.mapping.ObfuscationMapping; +import org.benf.cfr.reader.relationship.MemberNameResolver; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.state.TypeUsageCollectingDumper; +import org.benf.cfr.reader.util.AnalysisType; +import org.benf.cfr.reader.util.CannotLoadClassException; +import org.benf.cfr.reader.util.collections.ListFactory; +import org.benf.cfr.reader.util.getopt.Options; +import org.benf.cfr.reader.util.getopt.OptionsImpl; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + + +public class CfrDecompiler implements Decompiler { + private final DCCommonState state; + + public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { + Map options = new HashMap<>(); + + state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() { + @Override + public JarContent addJarContent(String s, AnalysisType analysisType) { + return null; + } + + @Override + public void informAnalysisRelativePathDetail(String usePath, String classFilePath) { + + } + + @Override + public Collection addJar(String jarPath) { + return null; + } + + @Override + public String getPossiblyRenamedPath(String path) { + return path; + } + + @Override + public Pair getClassFileContent(String path) { + ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); + + if (node == null) { + try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) { + if (classResource != null) { + return new Pair<>(ByteStreams.toByteArray(classResource), path); + } + } catch (IOException ignored) {} + + return null; + } + + ClassWriter cw = new ClassWriter(0); + node.accept(cw); + return new Pair<>(cw.toByteArray(), path); + } + }); + } + + @Override + public Source getSource(String className) { + DCCommonState state = this.state; + Options options = state.getOptions(); + + ObfuscationMapping mapping = MappingFactory.get(options, state); + state = new DCCommonState(state, mapping); + ClassFile tree = state.getClassFileMaybePath(className); + + state.configureWith(tree); + + // To make sure we're analysing the cached version + try { + tree = state.getClassFile(tree.getClassType()); + } catch (CannotLoadClassException ignored) {} + + if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) { + tree.loadInnerClasses(state); + } + + if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) { + MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes())); + } + + TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree); + tree.analyseTop(state, typeUsageCollector); + return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation()); + } +} diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java new file mode 100644 index 0000000..d4f2da6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.cfr; + +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import org.benf.cfr.reader.entities.ClassFile; +import org.benf.cfr.reader.state.DCCommonState; +import org.benf.cfr.reader.state.TypeUsageInformation; + +public class CfrSource implements Source { + private final ClassFile tree; + private final SourceIndex index; + private final String string; + + public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) { + this.tree = tree; + + EnigmaDumper dumper = new EnigmaDumper(typeUsages); + tree.dump(state.getObfuscationMapping().wrap(dumper)); + index = dumper.getIndex(); + string = dumper.getString(); + } + + @Override + public String asString() { + return string; + } + + @Override + public Source addJavadocs(EntryRemapper remapper) { + return this; // TODO + } + + @Override + public SourceIndex index() { + return index; + } +} diff --git a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java new file mode 100644 index 0000000..b9cdbea --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java @@ -0,0 +1,427 @@ +package cuchaz.enigma.source.cfr; + +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; +import org.benf.cfr.reader.bytecode.analysis.types.*; +import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable; +import org.benf.cfr.reader.entities.Field; +import org.benf.cfr.reader.entities.Method; +import org.benf.cfr.reader.mapping.NullMapping; +import org.benf.cfr.reader.mapping.ObfuscationMapping; +import org.benf.cfr.reader.state.TypeUsageInformation; +import org.benf.cfr.reader.util.collections.SetFactory; +import org.benf.cfr.reader.util.output.DelegatingDumper; +import org.benf.cfr.reader.util.output.Dumpable; +import org.benf.cfr.reader.util.output.Dumper; +import org.benf.cfr.reader.util.output.TypeContext; + +import java.util.Set; +import java.util.stream.Collectors; + +public class EnigmaDumper implements Dumper { + private int outputCount = 0; + private int indent; + private boolean atStart = true; + private boolean pendingCR = false; + private final StringBuilder sb = new StringBuilder(); + private final TypeUsageInformation typeUsageInformation; + private final Set emitted = SetFactory.newSet(); + private final SourceIndex index = new SourceIndex(); + private int position; + + + public EnigmaDumper(TypeUsageInformation typeUsageInformation) { + this.typeUsageInformation = typeUsageInformation; + } + + private void append(String s) { + sb.append(s); + position += s.length(); + } + + private String getDesc(JavaTypeInstance type) { + type = type.getDeGenerifiedType(); + + if (type instanceof JavaRefTypeInstance) { + return "L" + type.getRawName().replace('.', '/') + ";"; + } + + if (type instanceof JavaArrayTypeInstance) { + return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection()); + } + + if (type instanceof RawJavaType) { + switch ((RawJavaType) type) { + case BOOLEAN: + return "Z"; + case BYTE: + return "B"; + case CHAR: + return "C"; + case SHORT: + return "S"; + case INT: + return "I"; + case LONG: + return "J"; + case FLOAT: + return "F"; + case DOUBLE: + return "D"; + case VOID: + return "V"; + default: + throw new AssertionError(); + } + } + + throw new AssertionError(); + } + + private MethodEntry getMethodEntry(MethodPrototype method) { + if (method.getClassType() == null) { + return null; + } + + MethodDescriptor desc = new MethodDescriptor( + method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()), + new TypeDescriptor(method.getName().equals("") || method.getName().equals("") ? "V" : getDesc(method.getReturnType())) + ); + + return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc); + } + + private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) { + int variableIndex = method.isInstanceMethod() ? 1 : 0; + for (int i = 0; i < parameterIndex; i++) { + variableIndex += method.getArgs().get(parameterIndex).getStackType().getComputationCategory(); + } + + return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null); + } + + private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) { + return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type))); + } + + private ClassEntry getClassEntry(JavaTypeInstance type) { + return new ClassEntry(type.getRawName().replace('.', '/')); + } + + @Override + public Dumper beginBlockComment(boolean inline) { + print("/*").newln(); + return this; + } + + @Override + public Dumper endBlockComment() { + print(" */").newln(); + return this; + } + + @Override + public Dumper label(String s, boolean inline) { + processPendingCR(); + append(s); + append(":"); + return this; + } + + @Override + public Dumper comment(String s) { + append("// "); + append(s); + append("\n"); + return this; + } + + @Override + public void enqueuePendingCarriageReturn() { + pendingCR = true; + } + + @Override + public Dumper removePendingCarriageReturn() { + pendingCR = false; + return this; + } + + private void processPendingCR() { + if (pendingCR) { + append("\n"); + atStart = true; + pendingCR = false; + } + } + + @Override + public Dumper identifier(String s, Object ref, boolean defines) { + return print(s); + } + + @Override + public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = getMethodEntry(method); + + if (entry != null) { + if (defines) { + index.addDeclaration(token, entry); + } else { + index.addReference(token, entry, null); + } + } + + return identifier(name, null, defines); + } + + @Override + public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = getParameterEntry(method, index, name); + + if (entry != null) { + if (defines) { + this.index.addDeclaration(token, entry); + } else { + this.index.addReference(token, entry, null); + } + } + + return identifier(name, null, defines); + } + + @Override + public Dumper variableName(String name, NamedVariable variable, boolean defines) { + return identifier(name, null, defines); + } + + @Override + public Dumper packageName(JavaRefTypeInstance t) { + String s = t.getPackageName(); + + if (!s.isEmpty()) { + keyword("package ").print(s).endCodeln().newln(); + } + + return this; + } + + @Override + public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) { + doIndent(); + Token token = new Token(position, position + name.length(), name); + Entry entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance()); + + if (entry != null) { + if (defines) { + index.addDeclaration(token, entry); + } else { + index.addReference(token, entry, null); + } + } + + identifier(name, null, defines); + return this; + } + + @Override + public Dumper print(String s) { + processPendingCR(); + doIndent(); + append(s); + atStart = s.endsWith("\n"); + outputCount++; + return this; + } + + @Override + public Dumper print(char c) { + return print(String.valueOf(c)); + } + + @Override + public Dumper newln() { + append("\n"); + atStart = true; + outputCount++; + return this; + } + + @Override + public Dumper endCodeln() { + append(";\n"); + atStart = true; + outputCount++; + return this; + } + + @Override + public Dumper keyword(String s) { + print(s); + return this; + } + + @Override + public Dumper operator(String s) { + print(s); + return this; + } + + @Override + public Dumper separator(String s) { + print(s); + return this; + } + + @Override + public Dumper literal(String s, Object o) { + print(s); + return this; + } + + private void doIndent() { + if (!atStart) return; + String indents = " "; + + for (int x = 0; x < indent; ++x) { + append(indents); + } + + atStart = false; + } + + @Override + public void indent(int diff) { + indent += diff; + } + + @Override + public Dumper dump(Dumpable d) { + if (d == null) { + keyword("null"); + return this; + } + + d.dump(this); + return this; + } + + @Override + public TypeUsageInformation getTypeUsageInformation() { + return typeUsageInformation; + } + + @Override + public ObfuscationMapping getObfuscationMapping() { + return NullMapping.INSTANCE; + } + + @Override + public String toString() { + return sb.toString(); + } + + @Override + public void addSummaryError(Method method, String s) {} + + @Override + public void close() { + } + + @Override + public boolean canEmitClass(JavaTypeInstance type) { + return emitted.add(type); + } + + @Override + public int getOutputCount() { + return outputCount; + } + + @Override + public Dumper dump(JavaTypeInstance type) { + return dump(type, TypeContext.None, false); + } + + @Override + public Dumper dump(JavaTypeInstance type, boolean defines) { + return dump(type, TypeContext.None, false); + } + + @Override + public Dumper dump(JavaTypeInstance type, TypeContext context) { + return dump(type, context, false); + } + + private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) { + doIndent(); + if (type instanceof JavaRefTypeInstance) { + int start = position; + type.dumpInto(this, typeUsageInformation, TypeContext.None); + int end = position; + Token token = new Token(start, end, sb.toString().substring(start, end)); + + if (defines) { + index.addDeclaration(token, getClassEntry(type)); + } else { + index.addReference(token, getClassEntry(type), null); + } + + return this; + } + + type.dumpInto(this, typeUsageInformation, context); + return this; + } + + @Override + public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { + return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation); + } + + public SourceIndex getIndex() { + index.setSource(getString()); + return index; + } + + public String getString() { + return sb.toString(); + } + + public static class WithTypeUsageInformationDumper extends DelegatingDumper { + private final TypeUsageInformation typeUsageInformation; + + WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) { + super(delegate); + this.typeUsageInformation = typeUsageInformation; + } + + @Override + public TypeUsageInformation getTypeUsageInformation() { + return typeUsageInformation; + } + + @Override + public Dumper dump(JavaTypeInstance javaTypeInstance) { + return dump(javaTypeInstance, TypeContext.None); + } + + @Override + public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) { + javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext); + return this; + } + + @Override + public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { + return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation); + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java new file mode 100644 index 0000000..2fae61a --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.Signature; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.FieldDefEntry; +import cuchaz.enigma.translation.representation.entry.MethodDefEntry; + +public class EntryParser { + public static FieldDefEntry parse(FieldDefinition definition) { + ClassEntry owner = parse(definition.getDeclaringType()); + TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature()); + Signature signature = Signature.createTypedSignature(definition.getSignature()); + AccessFlags access = new AccessFlags(definition.getModifiers()); + return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null); + } + + public static ClassDefEntry parse(TypeDefinition def) { + String name = def.getInternalName(); + Signature signature = Signature.createSignature(def.getSignature()); + AccessFlags access = new AccessFlags(def.getModifiers()); + ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null; + ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new); + return new ClassDefEntry(name, signature, access, superClass, interfaces); + } + + public static ClassEntry parse(TypeReference typeReference) { + return new ClassEntry(typeReference.getInternalName()); + } + + public static MethodDefEntry parse(MethodDefinition definition) { + ClassEntry classEntry = parse(definition.getDeclaringType()); + MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature()); + Signature signature = Signature.createSignature(definition.getSignature()); + AccessFlags access = new AccessFlags(definition.getModifiers()); + return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null); + } + + public static TypeDescriptor parseTypeDescriptor(TypeReference type) { + return new TypeDescriptor(type.getErasedSignature()); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java new file mode 100644 index 0000000..37bc0c8 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java @@ -0,0 +1,81 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.languages.java.BraceStyle; +import com.strobel.decompiler.languages.java.JavaFormattingOptions; +import com.strobel.decompiler.languages.java.ast.AstBuilder; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.api.EnigmaPluginContext; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.SourceSettings; +import cuchaz.enigma.source.procyon.transformers.*; +import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; +import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; +import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; +import cuchaz.enigma.utils.Utils; + +public class ProcyonDecompiler implements Decompiler { + private final SourceSettings settings; + private final DecompilerSettings decompilerSettings; + private final MetadataSystem metadataSystem; + + public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { + ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); + + metadataSystem = new NoRetryMetadataSystem(typeLoader); + metadataSystem.setEagerMethodLoadingEnabled(true); + + decompilerSettings = DecompilerSettings.javaDefaults(); + decompilerSettings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); + decompilerSettings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); + decompilerSettings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); + decompilerSettings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); + decompilerSettings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); + decompilerSettings.setTypeLoader(typeLoader); + + JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions(); + formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine; + formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine; + formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine; + + this.settings = settings; + } + + @Override + public Source getSource(String className) { + TypeReference type = metadataSystem.lookupType(className); + if (type == null) { + throw new Error(String.format("Unable to find desc: %s", className)); + } + + TypeDefinition resolvedType = type.resolve(); + + DecompilerContext context = new DecompilerContext(); + context.setCurrentType(resolvedType); + context.setSettings(decompilerSettings); + + AstBuilder builder = new AstBuilder(context); + builder.addType(resolvedType); + builder.runTransformations(null); + CompilationUnit source = builder.getCompilationUnit(); + + new ObfuscatedEnumSwitchRewriterTransform(context).run(source); + new VarargsFixer(context).run(source); + new RemoveObjectCasts(context).run(source); + new Java8Generics().run(source); + new InvalidIdentifierFix().run(source); + if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source); + if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source); + source.acceptVisitor(new InsertParenthesesVisitor(), null); + + return new ProcyonSource(source, decompilerSettings); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java new file mode 100644 index 0000000..53c8c70 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java @@ -0,0 +1,49 @@ +package cuchaz.enigma.source.procyon; + +import com.strobel.decompiler.DecompilerSettings; +import com.strobel.decompiler.PlainTextOutput; +import com.strobel.decompiler.languages.java.JavaOutputVisitor; +import com.strobel.decompiler.languages.java.ast.CompilationUnit; +import cuchaz.enigma.source.Source; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.index.SourceIndexVisitor; +import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform; +import cuchaz.enigma.translation.mapping.EntryRemapper; + +import java.io.StringWriter; + +public class ProcyonSource implements Source { + private final DecompilerSettings settings; + private final CompilationUnit tree; + private String string; + + public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) { + this.settings = settings; + this.tree = tree; + } + + @Override + public SourceIndex index() { + SourceIndex index = new SourceIndex(asString()); + tree.acceptVisitor(new SourceIndexVisitor(), index); + return index; + } + + @Override + public String asString() { + if (string == null) { + StringWriter writer = new StringWriter(); + tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); + string = writer.toString(); + } + + return string; + } + + @Override + public Source addJavadocs(EntryRemapper remapper) { + CompilationUnit remappedTree = (CompilationUnit) tree.clone(); + new AddJavadocsAstTransform(remapper).run(remappedTree); + return new ProcyonSource(remappedTree, settings); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java new file mode 100644 index 0000000..f6eeb15 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java @@ -0,0 +1,95 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.index; + +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.entry.*; + +public class SourceIndexClassVisitor extends SourceIndexVisitor { + private ClassDefEntry classEntry; + + public SourceIndexClassVisitor(ClassDefEntry classEntry) { + this.classEntry = classEntry; + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + // is this this class, or a subtype? + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassDefEntry classEntry = EntryParser.parse(def); + if (!classEntry.equals(this.classEntry)) { + // it's a subtype, recurse + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + return visitChildren(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + MethodDefEntry methodEntry = EntryParser.parse(def); + AstNode tokenNode = node.getNameToken(); + if (methodEntry.isConstructor() && methodEntry.getName().equals("")) { + // for static initializers, check elsewhere for the token node + tokenNode = node.getModifiers().firstOrNullObject(); + } + index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { + MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); + MethodDefEntry methodEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry); + return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldDefEntry fieldEntry = EntryParser.parse(def); + assert (node.getVariables().size() == 1); + VariableInitializer variable = node.getVariables().firstOrNullObject(); + index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry); + return visitChildren(node, index); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { + // treat enum declarations as field declarations + FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); + FieldDefEntry fieldEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry); + return visitChildren(node, index); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java new file mode 100644 index 0000000..0e8bc51 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java @@ -0,0 +1,218 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; +import com.strobel.assembler.metadata.*; +import com.strobel.decompiler.ast.Variable; +import com.strobel.decompiler.languages.TextLocation; +import com.strobel.decompiler.languages.java.ast.*; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; +import cuchaz.enigma.translation.representation.entry.*; + +import java.lang.Error; +import java.util.HashMap; +import java.util.Map; + +public class SourceIndexMethodVisitor extends SourceIndexVisitor { + private final MethodDefEntry methodEntry; + + private Multimap unmatchedIdentifier = HashMultimap.create(); + private Map> identifierEntryCache = new HashMap<>(); + + public SourceIndexMethodVisitor(MethodDefEntry methodEntry) { + this.methodEntry = methodEntry; + } + + @Override + public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + // get the behavior entry + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = null; + if (ref instanceof MethodReference) { + methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); + } + if (methodEntry != null) { + // get the node for the token + AstNode tokenNode = null; + if (node.getTarget() instanceof MemberReferenceExpression) { + tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); + } else if (node.getTarget() instanceof SuperReferenceExpression) { + tokenNode = node.getTarget(); + } else if (node.getTarget() instanceof ThisReferenceExpression) { + tokenNode = node.getTarget(); + } + if (tokenNode != null) { + index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry); + } + } + + // Check for identifier + node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) + .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); + return visitChildren(node, index); + } + + @Override + public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref instanceof FieldReference) { + // make sure this is actually a field + String erasedSignature = ref.getErasedSignature(); + if (erasedSignature.indexOf('(') >= 0) { + throw new Error("Expected a field here! got " + ref); + } + + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); + index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitSimpleType(SimpleType node, SourceIndex index) { + TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); + if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { + ClassEntry classEntry = new ClassEntry(ref.getInternalName()); + index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { + ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); + int parameterIndex = def.getSlot(); + + if (parameterIndex >= 0) { + MethodDefEntry ownerMethod = methodEntry; + if (def.getMethod() instanceof MethodDefinition) { + ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod()); + } + + TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType()); + LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null); + Identifier identifier = node.getNameToken(); + // cache the argument entry and the identifier + identifierEntryCache.put(identifier.getName(), localVariableEntry); + index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null) { + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); + index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry); + } else + this.checkIdentifier(node, index); + return visitChildren(node, index); + } + + private void checkIdentifier(IdentifierExpression node, SourceIndex index) { + if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! + index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier())); + else + unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! + } + + private void addDeclarationToUnmatched(String key, SourceIndex index) { + Entry entry = identifierEntryCache.get(key); + + // This cannot happened in theory + if (entry == null) + return; + for (Identifier identifier : unmatchedIdentifier.get(key)) + index.addDeclaration(TokenFactory.createToken(index, identifier), entry); + unmatchedIdentifier.removeAll(key); + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + if (ref != null && node.getType() instanceof SimpleType) { + SimpleType simpleTypeNode = (SimpleType) node.getType(); + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry constructorEntry = new MethodEntry(classEntry, "", new MethodDescriptor(ref.getErasedSignature())); + index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry); + } + + return visitChildren(node, index); + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { + AstNodeCollection variables = node.getVariables(); + + // Single assignation + if (variables.size() == 1) { + VariableInitializer initializer = variables.firstOrNullObject(); + if (initializer != null && node.getType() instanceof SimpleType) { + Identifier identifier = initializer.getNameToken(); + Variable variable = initializer.getUserData(Keys.VARIABLE); + if (variable != null) { + VariableDefinition originalVariable = variable.getOriginalVariable(); + if (originalVariable != null) { + int variableIndex = originalVariable.getSlot(); + if (variableIndex >= 0) { + MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod()); + TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType()); + LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null); + identifierEntryCache.put(identifier.getName(), localVariableEntry); + addDeclarationToUnmatched(identifier.getName(), index); + index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); + } + } + } + } + } + return visitChildren(node, index); + } + + @Override + public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { + MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); + + if (ref instanceof MethodReference) { + // get the behavior entry + ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); + MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); + + // get the node for the token + AstNode methodNameToken = node.getMethodNameToken(); + AstNode targetToken = node.getTarget(); + + if (methodNameToken != null) { + index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry); + } + + if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) { + index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry); + } + } + + return visitChildren(node, index); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java new file mode 100644 index 0000000..dad505f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.index; + +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.representation.entry.ClassDefEntry; + +public class SourceIndexVisitor extends DepthFirstAstVisitor { + @Override + public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { + TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); + ClassDefEntry classEntry = EntryParser.parse(def); + index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); + + return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); + } + + @Override + protected Void visitChildren(AstNode node, SourceIndex index) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, index); + } + return null; + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java new file mode 100644 index 0000000..db90ffa --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java @@ -0,0 +1,41 @@ +package cuchaz.enigma.source.procyon.index; + +import com.strobel.decompiler.languages.Region; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import cuchaz.enigma.analysis.Token; +import cuchaz.enigma.source.SourceIndex; + +import java.util.regex.Pattern; + +public class TokenFactory { + private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); + + public static Token createToken(SourceIndex index, AstNode node) { + String name = node instanceof Identifier ? ((Identifier) node).getName() : ""; + Region region = node.getRegion(); + + int start = index.getPosition(region.getBeginLine(), region.getBeginColumn()); + int end = index.getPosition(region.getEndLine(), region.getEndColumn()); + String text = index.getSource().substring(start, end); + Token token = new Token(start, end, text); + + boolean isAnonymousInner = + node instanceof Identifier && + name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && + name.lastIndexOf('$') >= 0 && + !ANONYMOUS_INNER.matcher(name).matches(); + + if (isAnonymousInner) { + TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; + if (type != null) { + name = type.getName(); + token.end = token.start + name.length(); + } + } + + return token; + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java new file mode 100644 index 0000000..70fc8c6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java @@ -0,0 +1,134 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.google.common.base.Function; +import com.google.common.base.Strings; +import com.strobel.assembler.metadata.ParameterDefinition; +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; +import cuchaz.enigma.source.procyon.EntryParser; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.stream.Stream; + +public final class AddJavadocsAstTransform implements IAstTransform { + + private final EntryRemapper remapper; + + public AddJavadocsAstTransform(EntryRemapper remapper) { + this.remapper = remapper; + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(remapper), null); + } + + static class Visitor extends DepthFirstAstVisitor { + + private final EntryRemapper remapper; + + Visitor(EntryRemapper remapper) { + this.remapper = remapper; + } + + private void addDoc(T node, Function> retriever) { + final Comment[] comments = getComments(node, retriever); + if (comments != null) { + node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments); + } + } + + private Comment[] getComments(T node, Function> retriever) { + final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); + final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc()); + return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st, + CommentType.Documentation)).toArray(Comment[]::new); + } + + private Comment[] getParameterComments(ParameterDeclaration node, Function> retriever) { + final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); + final Comment[] ret = getComments(node, retriever); + if (ret != null) { + final String paramPrefix = "@param " + mapping.getTargetName() + " "; + final String indent = Strings.repeat(" ", paramPrefix.length()); + ret[0].setContent(paramPrefix + ret[0].getContent()); + for (int i = 1; i < ret.length; i++) { + ret[i].setContent(indent + ret[i].getContent()); + } + } + return ret; + } + + private void visitMethod(AstNode node) { + final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION)); + final Comment[] baseComments = getComments(node, $ -> methodDefEntry); + List comments = new ArrayList<>(); + if (baseComments != null) + Collections.addAll(comments, baseComments); + + for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) { + ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION); + final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(), + true, + EntryParser.parseTypeDescriptor(def.getParameterType()), null)); + if (paramComments != null) + Collections.addAll(comments, paramComments); + } + + if (!comments.isEmpty()) { + if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) { + comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation)); + } + final AstNode oldFirst = node.getFirstChild(); + for (Comment comment : comments) { + node.insertChildBefore(oldFirst, comment, Roles.COMMENT); + } + } + } + + @Override + protected Void visitChildren(AstNode node, Void data) { + for (final AstNode child : node.getChildren()) { + child.acceptVisitor(this, data); + } + return null; + } + + @Override + public Void visitMethodDeclaration(MethodDeclaration node, Void data) { + visitMethod(node); + return super.visitMethodDeclaration(node, data); + } + + @Override + public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) { + visitMethod(node); + return super.visitConstructorDeclaration(node, data); + } + + @Override + public Void visitFieldDeclaration(FieldDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); + return super.visitFieldDeclaration(node, data); + } + + @Override + public Void visitTypeDeclaration(TypeDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION))); + return super.visitTypeDeclaration(node, data); + } + + @Override + public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) { + addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); + return super.visitEnumValueDeclaration(node, data); + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java new file mode 100644 index 0000000..39e599d --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.ImportDeclaration; +import com.strobel.decompiler.languages.java.ast.PackageDeclaration; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +public final class DropImportAstTransform implements IAstTransform { + public static final DropImportAstTransform INSTANCE = new DropImportAstTransform(); + + private DropImportAstTransform() { + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor { + @Override + public Void visitPackageDeclaration(PackageDeclaration node, Void data) { + node.remove(); + return null; + } + + @Override + public Void visitImportDeclaration(ImportDeclaration node, Void data) { + node.remove(); + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java new file mode 100644 index 0000000..b8c087b --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java @@ -0,0 +1,37 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.*; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +import javax.lang.model.element.Modifier; + +public final class DropVarModifiersAstTransform implements IAstTransform { + public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform(); + + private DropVarModifiersAstTransform() { + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor { + @Override + public Void visitParameterDeclaration(ParameterDeclaration node, Void data) { + for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) { + if (modifierToken.getModifier() == Modifier.FINAL) { + modifierToken.remove(); + } + } + + return null; + } + + @Override + public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { + node.removeModifier(Modifier.FINAL); + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java new file mode 100644 index 0000000..34d95fa --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java @@ -0,0 +1,29 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 13/07/2018. + */ +public class InvalidIdentifierFix implements IAstTransform { + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + class Visitor extends DepthFirstAstVisitor{ + @Override + public Void visitIdentifier(Identifier node, Void data) { + super.visitIdentifier(node, data); + if (node.getName().equals("do") || node.getName().equals("if")){ + Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation()); + newIdentifier.copyUserDataFrom(node); + node.replaceWith(newIdentifier); + } + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java new file mode 100644 index 0000000..8accfc7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java @@ -0,0 +1,107 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.assembler.metadata.CommonTypeReferences; +import com.strobel.assembler.metadata.Flags; +import com.strobel.assembler.metadata.IGenericInstance; +import com.strobel.assembler.metadata.IMemberDefinition; +import com.strobel.assembler.metadata.JvmType; +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.AstNodeCollection; +import com.strobel.decompiler.languages.java.ast.AstType; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ComposedType; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.Identifier; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.Roles; +import com.strobel.decompiler.languages.java.ast.SimpleType; +import com.strobel.decompiler.languages.java.ast.WildcardType; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 12/07/2018. + */ +public class Java8Generics implements IAstTransform { + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(), null); + } + + static class Visitor extends DepthFirstAstVisitor{ + + @Override + public Void visitInvocationExpression(InvocationExpression node, Void data) { + super.visitInvocationExpression(node, data); + if (node.getTarget() instanceof MemberReferenceExpression){ + MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget(); + if (referenceExpression.getTypeArguments().stream().map(t->{ + TypeReference tr = t.toTypeReference(); + if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below + TypeReference resolved = tr.resolve(); + if (resolved != null) + return resolved; + } + return tr; + }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) { + //these are invalid for invocations, let the compiler work it out + referenceExpression.getTypeArguments().clear(); + } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){ + //all are , thereby redundant and/or bad + referenceExpression.getTypeArguments().clear(); + } + } + return null; + } + + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { + super.visitObjectCreationExpression(node, data); + AstType type = node.getType(); + if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){ + SimpleType simpleType = (SimpleType) type; + AstNodeCollection typeArguments = simpleType.getTypeArguments(); + if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){ + //all are , thereby redundant and/or bad + typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create("")); + } + } + return null; + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + boolean doReplace = false; + TypeReference typeReference = node.getType().toTypeReference(); + if (typeReference.isArray() && typeReference.getElementType().isGenericType()){ + doReplace = true; + } else if (typeReference.isGenericType()) { + Expression target = node.getExpression(); + if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){ + doReplace = true; + } else if (target instanceof InvocationExpression) { + InvocationExpression invocationExpression = (InvocationExpression)target; + if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) { + ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear(); + doReplace = true; + } + } + } + super.visitCastExpression(node, data); + if (doReplace){ + node.replaceWith(node.getExpression()); + } + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java new file mode 100644 index 0000000..32bb72f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java @@ -0,0 +1,414 @@ +/* + * Originally: + * EnumSwitchRewriterTransform.java + * + * Copyright (c) 2013 Mike Strobel + * + * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; + * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. + * + * This source code is subject to terms and conditions of the Apache License, Version 2.0. + * A copy of the license can be found in the License.html file at the root of this distribution. + * By using this source code in any fashion, you are agreeing to be bound by the terms of the + * Apache License, Version 2.0. + * + * You must not remove this notice, or any other, from this software. + */ + +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.assembler.metadata.FieldDefinition; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.SafeCloseable; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AssignmentExpression; +import com.strobel.decompiler.languages.java.ast.AstBuilder; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CaseLabel; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.IdentifierExpression; +import com.strobel.decompiler.languages.java.ast.IndexerExpression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; +import com.strobel.decompiler.languages.java.ast.SwitchSection; +import com.strobel.decompiler.languages.java.ast.SwitchStatement; +import com.strobel.decompiler.languages.java.ast.TypeDeclaration; +import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to: + * - Not rely on a field containing "$SwitchMap$" (Proguard strips it) + * - Ignore classes *with* SwitchMap$ names (so the original can handle it) + * - Ignores inner synthetics that are not package private + */ +@SuppressWarnings("Duplicates") +public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform { + private final DecompilerContext _context; + + public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) { + _context = VerifyArgument.notNull(context, "context"); + } + + @Override + public void run(final AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor { + private final static class SwitchMapInfo { + final String enclosingType; + final Map> switches = new LinkedHashMap<>(); + final Map> mappings = new LinkedHashMap<>(); + + TypeDeclaration enclosingTypeDeclaration; + + SwitchMapInfo(final String enclosingType) { + this.enclosingType = enclosingType; + } + } + + private final Map _switchMaps = new LinkedHashMap<>(); + private boolean _isSwitchMapWrapper; + + protected Visitor(final DecompilerContext context) { + super(context); + } + + @Override + public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { + final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; + final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); + final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); + + if (isSwitchMapWrapper) { + final String internalName = typeDefinition.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(internalName); + + if (info == null) { + _switchMaps.put(internalName, info = new SwitchMapInfo(internalName)); + } + + info.enclosingTypeDeclaration = typeDeclaration; + } + + _isSwitchMapWrapper = isSwitchMapWrapper; + + try { + super.visitTypeDeclaration(typeDeclaration, p); + } + finally { + _isSwitchMapWrapper = oldIsSwitchMapWrapper; + } + + rewrite(); + + return null; + } + + @Override + public Void visitSwitchStatement(final SwitchStatement node, final Void data) { + final Expression test = node.getExpression(); + + if (test instanceof IndexerExpression) { + final IndexerExpression indexer = (IndexerExpression) test; + final Expression array = indexer.getTarget(); + final Expression argument = indexer.getArgument(); + + if (!(array instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array; + final Expression arrayOwner = arrayAccess.getTarget(); + final String mapName = arrayAccess.getMemberName(); + + if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner; + final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE); + + if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) { + return super.visitSwitchStatement(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitSwitchStatement(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + + if (!"ordinal".equals(memberReference.getMemberName())) { + return super.visitSwitchStatement(node, data); + } + + final String enclosingTypeName = enclosingType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingTypeName); + + if (info == null) { + _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName)); + + final TypeDefinition resolvedType = enclosingType.resolve(); + + if (resolvedType != null) { + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + try (final SafeCloseable importSuppression = astBuilder.suppressImports()) { + final TypeDeclaration declaration = astBuilder.createType(resolvedType); + + declaration.acceptVisitor(this, data); + } + } + } + + List switches = info.switches.get(mapName); + + if (switches == null) { + info.switches.put(mapName, switches = new ArrayList<>()); + } + + switches.add(node); + } + + return super.visitSwitchStatement(node, data); + } + + @Override + public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) { + final TypeDefinition currentType = context.getCurrentType(); + final MethodDefinition currentMethod = context.getCurrentMethod(); + + if (_isSwitchMapWrapper && + currentType != null && + currentMethod != null && + currentMethod.isTypeInitializer()) { + + final Expression left = node.getLeft(); + final Expression right = node.getRight(); + + if (left instanceof IndexerExpression && + right instanceof PrimitiveExpression) { + + String mapName = null; + + final Expression array = ((IndexerExpression) left).getTarget(); + final Expression argument = ((IndexerExpression) left).getArgument(); + + if (array instanceof MemberReferenceExpression) { + mapName = ((MemberReferenceExpression) array).getMemberName(); + } + else if (array instanceof IdentifierExpression) { + mapName = ((IdentifierExpression) array).getIdentifier(); + } + + if (mapName == null || mapName.startsWith("$SwitchMap$")) { + return super.visitAssignmentExpression(node, data); + } + + if (!(argument instanceof InvocationExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final InvocationExpression invocation = (InvocationExpression) argument; + final Expression invocationTarget = invocation.getTarget(); + + if (!(invocationTarget instanceof MemberReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; + final Expression memberTarget = memberReference.getTarget(); + + if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) { + return super.visitAssignmentExpression(node, data); + } + + final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget; + final Expression outerMemberTarget = outerMemberReference.getTarget(); + + if (!(outerMemberTarget instanceof TypeReferenceExpression)) { + return super.visitAssignmentExpression(node, data); + } + + final String enclosingType = currentType.getInternalName(); + + SwitchMapInfo info = _switchMaps.get(enclosingType); + + if (info == null) { + _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType)); + + AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); + + if (astBuilder == null) { + astBuilder = new AstBuilder(context); + } + + info.enclosingTypeDeclaration = astBuilder.createType(currentType); + } + + final PrimitiveExpression value = (PrimitiveExpression) right; + + assert value.getValue() instanceof Integer; + + Map mapping = info.mappings.get(mapName); + + if (mapping == null) { + info.mappings.put(mapName, mapping = new LinkedHashMap<>()); + } + + final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName()); + + enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE)); + + mapping.put(((Number) value.getValue()).intValue(), enumValue); + } + } + + return super.visitAssignmentExpression(node, data); + } + + private void rewrite() { + if (_switchMaps.isEmpty()) { + return; + } + + for (final SwitchMapInfo info : _switchMaps.values()) { + rewrite(info); + } + + // + // Remove switch map type wrappers that are no longer referenced. + // + + outer: + for (final SwitchMapInfo info : _switchMaps.values()) { + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + + if (switches != null && !switches.isEmpty()) { + continue outer; + } + } + + final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration; + + if (enclosingTypeDeclaration != null) { + enclosingTypeDeclaration.remove(); + } + } + } + + private void rewrite(final SwitchMapInfo info) { + if (info.switches.isEmpty()) { + return; + } + + for (final String mapName : info.switches.keySet()) { + final List switches = info.switches.get(mapName); + final Map mappings = info.mappings.get(mapName); + + if (switches != null && mappings != null) { + for (int i = 0; i < switches.size(); i++) { + if (rewriteSwitch(switches.get(i), mappings)) { + switches.remove(i--); + } + } + } + } + } + + private boolean rewriteSwitch(final SwitchStatement s, final Map mappings) { + final Map replacements = new IdentityHashMap<>(); + + for (final SwitchSection section : s.getSwitchSections()) { + for (final CaseLabel caseLabel : section.getCaseLabels()) { + final Expression expression = caseLabel.getExpression(); + + if (expression.isNull()) { + continue; + } + + if (expression instanceof PrimitiveExpression) { + final Object value = ((PrimitiveExpression) expression).getValue(); + + if (value instanceof Integer) { + final Expression replacement = mappings.get(value); + + if (replacement != null) { + replacements.put(expression, replacement); + continue; + } + } + } + + // + // If we can't rewrite all cases, we abort. + // + + return false; + } + } + + final IndexerExpression indexer = (IndexerExpression) s.getExpression(); + final InvocationExpression argument = (InvocationExpression) indexer.getArgument(); + final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget(); + final Expression newTest = memberReference.getTarget(); + + newTest.remove(); + indexer.replaceWith(newTest); + + for (final Map.Entry entry : replacements.entrySet()) { + entry.getKey().replaceWith(entry.getValue().clone()); + } + + return true; + } + + private static boolean isSwitchMapWrapper(final TypeReference type) { + if (type == null) { + return false; + } + + final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type + : type.resolve(); + + if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) { + return false; + } + + for (final FieldDefinition field : definition.getDeclaredFields()) { + if (!field.getName().startsWith("$SwitchMap$") && + BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) { + + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java new file mode 100644 index 0000000..cf0376f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java @@ -0,0 +1,39 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.BuiltinTypes; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; + +/** + * Created by Thiakil on 11/07/2018. + */ +public class RemoveObjectCasts implements IAstTransform { + private final DecompilerContext _context; + + public RemoveObjectCasts(DecompilerContext context) { + _context = context; + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + private final static class Visitor extends ContextTrackingVisitor{ + + protected Visitor(DecompilerContext context) { + super(context); + } + + @Override + public Void visitCastExpression(CastExpression node, Void data) { + if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){ + node.replaceWith(node.getExpression()); + } + return super.visitCastExpression(node, data); + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java new file mode 100644 index 0000000..d3ddaab --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java @@ -0,0 +1,197 @@ +package cuchaz.enigma.source.procyon.transformers; + +import com.strobel.assembler.metadata.MemberReference; +import com.strobel.assembler.metadata.MetadataFilters; +import com.strobel.assembler.metadata.MetadataHelper; +import com.strobel.assembler.metadata.MethodBinder; +import com.strobel.assembler.metadata.MethodDefinition; +import com.strobel.assembler.metadata.MethodReference; +import com.strobel.assembler.metadata.TypeReference; +import com.strobel.core.StringUtilities; +import com.strobel.core.VerifyArgument; +import com.strobel.decompiler.DecompilerContext; +import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; +import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; +import com.strobel.decompiler.languages.java.ast.AstNode; +import com.strobel.decompiler.languages.java.ast.AstNodeCollection; +import com.strobel.decompiler.languages.java.ast.CastExpression; +import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; +import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; +import com.strobel.decompiler.languages.java.ast.Expression; +import com.strobel.decompiler.languages.java.ast.InvocationExpression; +import com.strobel.decompiler.languages.java.ast.JavaResolver; +import com.strobel.decompiler.languages.java.ast.Keys; +import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; +import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; +import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; +import com.strobel.decompiler.semantics.ResolveResult; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Thiakil on 12/07/2018. + */ +public class VarargsFixer implements IAstTransform { + private final DecompilerContext _context; + + public VarargsFixer(final DecompilerContext context) { + _context = VerifyArgument.notNull(context, "context"); + } + + @Override + public void run(AstNode compilationUnit) { + compilationUnit.acceptVisitor(new Visitor(_context), null); + } + + class Visitor extends ContextTrackingVisitor { + private final JavaResolver _resolver; + protected Visitor(DecompilerContext context) { + super(context); + _resolver = new JavaResolver(context); + } + + //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them + @Override + public Void visitInvocationExpression(InvocationExpression node, Void data) { + super.visitInvocationExpression(node, data); + MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE); + if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){ + AstNodeCollection arguments = node.getArguments(); + Expression lastParam = arguments.lastOrNullObject(); + if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){ + ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam; + if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){ + lastParam.remove(); + } else { + for (Expression e : varargArray.getInitializer().getElements()){ + arguments.insertBefore(varargArray, e.clone()); + } + varargArray.remove(); + } + } + } + return null; + } + + //applies the vararg transform to object creation + @Override + public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { + super.visitObjectCreationExpression(node, data); + final AstNodeCollection arguments = node.getArguments(); + final Expression lastArgument = arguments.lastOrNullObject(); + + Expression arrayArg = lastArgument; + + if (arrayArg instanceof CastExpression) + arrayArg = ((CastExpression) arrayArg).getExpression(); + + if (arrayArg == null || + arrayArg.isNull() || + !(arrayArg instanceof ArrayCreationExpression && + node.getTarget() instanceof MemberReferenceExpression)) { + + return null; + } + + final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg; + final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget(); + + if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) { + return null; + } + + final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE); + + if (method == null) { + return null; + } + + final MethodDefinition resolved = method.resolve(); + + if (resolved == null || !resolved.isVarArgs()) { + return null; + } + + final List candidates; + final Expression invocationTarget = target.getTarget(); + + if (invocationTarget == null || invocationTarget.isNull()) { + candidates = MetadataHelper.findMethods( + context.getCurrentType(), + MetadataFilters.matchName(resolved.getName()) + ); + } + else { + final ResolveResult targetResult = _resolver.apply(invocationTarget); + + if (targetResult == null || targetResult.getType() == null) { + return null; + } + + candidates = MetadataHelper.findMethods( + targetResult.getType(), + MetadataFilters.matchName(resolved.getName()) + ); + } + + final List argTypes = new ArrayList<>(); + + for (final Expression argument : arguments) { + final ResolveResult argResult = _resolver.apply(argument); + + if (argResult == null || argResult.getType() == null) { + return null; + } + + argTypes.add(argResult.getType()); + } + + final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes); + + if (c1.isFailure() || c1.isAmbiguous()) { + return null; + } + + argTypes.remove(argTypes.size() - 1); + + final ArrayInitializerExpression initializer = newArray.getInitializer(); + final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty(); + + if (hasElements) { + for (final Expression argument : initializer.getElements()) { + final ResolveResult argResult = _resolver.apply(argument); + + if (argResult == null || argResult.getType() == null) { + return null; + } + + argTypes.add(argResult.getType()); + } + } + + final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes); + + if (c2.isFailure() || + c2.isAmbiguous() || + !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) { + + return null; + } + + lastArgument.remove(); + + if (!hasElements) { + lastArgument.remove(); + return null; + } + + for (final Expression newArg : initializer.getElements()) { + newArg.remove(); + arguments.add(newArg); + } + + return null; + } + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java new file mode 100644 index 0000000..e702956 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ClasspathTypeLoader; +import com.strobel.assembler.metadata.ITypeLoader; + +/** + * Caching version of {@link ClasspathTypeLoader} + */ +public class CachingClasspathTypeLoader extends CachingTypeLoader { + private static ITypeLoader extraClassPathLoader = null; + + public static void setExtraClassPathLoader(ITypeLoader loader){ + extraClassPathLoader = loader; + } + + private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); + + @Override + protected byte[] doLoad(String className) { + Buffer parentBuf = new Buffer(); + if (classpathLoader.tryLoadType(className, parentBuf)) { + return parentBuf.array(); + } + if (extraClassPathLoader != null){ + parentBuf.reset(); + if (extraClassPathLoader.tryLoadType(className, parentBuf)){ + return parentBuf.array(); + } + } + return EMPTY_ARRAY;//need to return *something* as null means no store + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java new file mode 100644 index 0000000..5be5ddd --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.google.common.collect.Maps; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +import java.util.Map; + +/** + * Common cache functions + */ +public abstract class CachingTypeLoader implements ITypeLoader { + protected static final byte[] EMPTY_ARRAY = {}; + + private final Map cache = Maps.newHashMap(); + + protected abstract byte[] doLoad(String className); + + @Override + public boolean tryLoadType(String className, Buffer out) { + + // check the cache + byte[] data = this.cache.computeIfAbsent(className, this::doLoad); + + if (data == EMPTY_ARRAY) { + return false; + } + + out.reset(data.length); + System.arraycopy(data, 0, out.array(), out.position(), data.length); + out.position(0); + return true; + } + + public void clearCache() { + this.cache.clear(); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java new file mode 100644 index 0000000..e703d3b --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2015 Jeff Martin. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser General Public + * License v3.0 which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/lgpl.html + *

+ * Contributors: + * Jeff Martin - initial API and implementation + ******************************************************************************/ + +package cuchaz.enigma.source.procyon.typeloader; + +import com.google.common.collect.Lists; +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; +import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +public class CompiledSourceTypeLoader extends CachingTypeLoader { + //Store one instance as the classpath shouldn't change during load + private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); + + private final ClassProvider compiledSource; + private final LinkedList> visitors = new LinkedList<>(); + + public CompiledSourceTypeLoader(ClassProvider compiledSource) { + this.compiledSource = compiledSource; + } + + public void addVisitor(Function visitor) { + this.visitors.addFirst(visitor); + } + + @Override + protected byte[] doLoad(String className) { + byte[] data = loadType(className); + if (data == null) { + return loadClasspath(className); + } + + return data; + } + + private byte[] loadClasspath(String name) { + Buffer parentBuf = new Buffer(); + if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { + return parentBuf.array(); + } + return EMPTY_ARRAY; + } + + private byte[] loadType(String className) { + ClassEntry entry = new ClassEntry(className); + + // find the class in the jar + ClassNode node = findClassNode(entry); + if (node == null) { + // couldn't find it + return null; + } + + removeRedundantClassCalls(node); + + ClassWriter writer = new ClassWriter(0); + + ClassVisitor visitor = writer; + for (Function visitorFunction : this.visitors) { + visitor = visitorFunction.apply(visitor); + } + + node.accept(visitor); + + // we have a transformed class! + return writer.toByteArray(); + } + + private void removeRedundantClassCalls(ClassNode node) { + // remove .getClass() calls that are seemingly injected + // DUP + // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; + // POP + for (MethodNode methodNode : node.methods) { + AbstractInsnNode insnNode = methodNode.instructions.getFirst(); + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { + AbstractInsnNode previous = methodInsnNode.getPrevious(); + AbstractInsnNode next = methodInsnNode.getNext(); + if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { + insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction + methodNode.instructions.remove(previous); + methodNode.instructions.remove(methodInsnNode); + methodNode.instructions.remove(next); + } + } + } + insnNode = insnNode.getNext(); + } + } + } + + private ClassNode findClassNode(ClassEntry entry) { + // try to find the class in the jar + for (String className : getClassNamesToTry(entry)) { + ClassNode node = compiledSource.getClassNode(className); + if (node != null) { + return node; + } + } + + // didn't find it ;_; + return null; + } + + private Collection getClassNamesToTry(ClassEntry entry) { + List classNamesToTry = Lists.newArrayList(); + classNamesToTry.add(entry.getFullName()); + + ClassEntry outerClass = entry.getOuterClass(); + if (outerClass != null) { + classNamesToTry.addAll(getClassNamesToTry(outerClass)); + } + + return classNamesToTry; + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java new file mode 100644 index 0000000..c4732b0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java @@ -0,0 +1,38 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.ITypeLoader; +import com.strobel.assembler.metadata.MetadataSystem; +import com.strobel.assembler.metadata.TypeDefinition; +import com.strobel.assembler.metadata.TypeReference; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public final class NoRetryMetadataSystem extends MetadataSystem { + private final Set failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public NoRetryMetadataSystem(final ITypeLoader typeLoader) { + super(typeLoader); + } + + @Override + protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { + if (failedTypes.contains(descriptor)) { + return null; + } + + final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); + + if (result == null) { + failedTypes.add(descriptor); + } + + return result; + } + + @Override + public synchronized TypeDefinition resolve(final TypeReference type) { + return super.resolve(type); + } +} diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java new file mode 100644 index 0000000..86c6ecc --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java @@ -0,0 +1,20 @@ +package cuchaz.enigma.source.procyon.typeloader; + +import com.strobel.assembler.metadata.Buffer; +import com.strobel.assembler.metadata.ITypeLoader; + +/** + * Typeloader with synchronized tryLoadType method + */ +public class SynchronizedTypeLoader implements ITypeLoader { + private final ITypeLoader delegate; + + public SynchronizedTypeLoader(ITypeLoader delegate) { + this.delegate = delegate; + } + + @Override + public synchronized boolean tryLoadType(String internalName, Buffer buffer) { + return delegate.tryLoadType(internalName, buffer); + } +} -- cgit v1.2.3