From df8def23dd0336d8a5d2369c5d4c0f4331838ef4 Mon Sep 17 00:00:00 2001 From: Erlend Åmdal Date: Wed, 15 May 2019 12:45:41 +0200 Subject: checkmappings command (#137) * Use expected map sizes for remapped multimaps * Index method and field types * Add package visibility index * Add checkmappings command and use System.err for error messages * Use exit codes for errors * Remove outer class check for package visible only refs * Throw exception on mapping error instead of exiting --- src/main/java/cuchaz/enigma/CommandMain.java | 62 ++++++++-- .../cuchaz/enigma/analysis/index/EntryIndex.java | 5 + .../cuchaz/enigma/analysis/index/JarIndex.java | 13 ++- .../analysis/index/PackageVisibilityIndex.java | 127 +++++++++++++++++++++ .../enigma/analysis/index/ReferenceIndex.java | 55 ++++++++- 5 files changed, 247 insertions(+), 15 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java (limited to 'src/main/java') diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index c9f83828..db4fd125 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java @@ -11,35 +11,47 @@ package cuchaz.enigma; +import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.serde.MappingFormat; import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Locale; +import java.util.Set; import java.util.jar.JarFile; +import java.util.stream.Collectors; public class CommandMain { public static void main(String[] args) throws Exception { try { // process the command - String command = getArg(args, 0, "command", true); - if (command.equalsIgnoreCase("deobfuscate")) { - deobfuscate(args); - } else if (command.equalsIgnoreCase("decompile")) { - decompile(args); - } else if (command.equalsIgnoreCase("convertmappings")) { - convertMappings(args); - } else { - throw new IllegalArgumentException("Command not recognized: " + command); + String command = getArg(args, 0, "command", true).toLowerCase(Locale.ROOT); + switch (command) { + case "deobfuscate": + deobfuscate(args); + break; + case "decompile": + decompile(args); + break; + case "convertmappings": + convertMappings(args); + break; + case "checkmappings": + checkMappings(args); + break; + default: + throw new IllegalArgumentException("Command not recognized: " + command); } } catch (IllegalArgumentException ex) { - System.out.println(ex.getMessage()); + System.err.println(ex.getMessage()); printHelp(); + System.exit(1); } } @@ -51,6 +63,7 @@ public class CommandMain { System.out.println("\t\tdeobfuscate []"); System.out.println("\t\tdecompile []"); System.out.println("\t\tconvertmappings "); + System.out.println("\t\tcheckmappings "); } private static void decompile(String[] args) throws Exception { @@ -100,6 +113,35 @@ public class CommandMain { saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); } + private static void checkMappings(String[] args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); + Path fileMappings = getReadablePath(getArg(args, 2, "enigma mapping", true)); + + System.out.println("Reading JAR..."); + Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); + System.out.println("Reading mappings..."); + + MappingFormat format = chooseEnigmaFormat(fileMappings); + EntryTree mappings = format.read(fileMappings, ProgressListener.VOID); + deobfuscator.setMappings(mappings); + + JarIndex idx = deobfuscator.getJarIndex(); + + boolean error = false; + + for (Set partition : idx.getPackageVisibilityIndex().getPartitions()) { + long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); + if (packages > 1) { + error = true; + System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); + } + } + + if (error) { + throw new Exception("Access violations detected"); + } + } + private static MappingFormat chooseEnigmaFormat(Path path) { if (Files.isDirectory(path)) { return MappingFormat.ENIGMA_DIRECTORY; diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java index 773eaf18..31c6f54f 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java @@ -64,6 +64,11 @@ public class EntryIndex implements JarIndexer { return fields.get(entry); } + @Nullable + public AccessFlags getClassAccess(ClassEntry entry) { + return classes.get(entry); + } + @Nullable public AccessFlags getEntryAccess(Entry entry) { if (entry instanceof MethodEntry) { diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java index a429ff6e..ac907af0 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java @@ -29,18 +29,20 @@ public class JarIndex implements JarIndexer { private final InheritanceIndex inheritanceIndex; private final ReferenceIndex referenceIndex; private final BridgeMethodIndex bridgeMethodIndex; + private final PackageVisibilityIndex packageVisibilityIndex; private final EntryResolver entryResolver; private final Collection indexers; private final Multimap methodImplementations = HashMultimap.create(); - public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex) { + public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { this.entryIndex = entryIndex; this.inheritanceIndex = inheritanceIndex; this.referenceIndex = referenceIndex; this.bridgeMethodIndex = bridgeMethodIndex; - this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); + this.packageVisibilityIndex = packageVisibilityIndex; + this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); this.entryResolver = new IndexEntryResolver(this); } @@ -49,7 +51,8 @@ public class JarIndex implements JarIndexer { InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); ReferenceIndex referenceIndex = new ReferenceIndex(); BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); - return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); + PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex(); + return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); } public void indexJar(ParsedJar jar, Consumer progress) { @@ -142,6 +145,10 @@ public class JarIndex implements JarIndexer { return bridgeMethodIndex; } + public PackageVisibilityIndex getPackageVisibilityIndex() { + return packageVisibilityIndex; + } + public EntryResolver getEntryResolver() { return entryResolver; } diff --git a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java new file mode 100644 index 00000000..9e9115fe --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java @@ -0,0 +1,127 @@ +package cuchaz.enigma.analysis.index; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import cuchaz.enigma.analysis.EntryReference; +import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.entry.*; + +import java.util.*; + +public class PackageVisibilityIndex implements JarIndexer { + private static boolean isPackageVisibleOnlyRef(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { + if (entryAcc.isPublic()) return false; + if (entryAcc.isProtected()) { + Set callerAncestors = inheritanceIndex.getAncestors(ref.context.getContainingClass()); + return !callerAncestors.contains(ref.entry.getContainingClass()); + } + return !entryAcc.isPrivate(); // if isPrivate is false, it must be package-private + } + + private final HashMultimap connections = HashMultimap.create(); + private final List> partitions = Lists.newArrayList(); + private final Map> classPartitions = Maps.newHashMap(); + + private void addConnection(ClassEntry classA, ClassEntry classB) { + connections.put(classA, classB); + connections.put(classB, classA); + } + + private void buildPartition(Set unassignedClasses, Set partition, ClassEntry member) { + for (ClassEntry connected : connections.get(member)) { + if (unassignedClasses.remove(connected)) { + partition.add(connected); + buildPartition(unassignedClasses, partition, connected); + } + } + } + + private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) { + for (FieldEntry entry : entryIndex.getFields()) { + AccessFlags entryAcc = entryIndex.getFieldAccess(entry); + if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { + for (EntryReference ref : referenceIndex.getReferencesToField(entry)) { + if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + } + } + + for (MethodEntry entry : entryIndex.getMethods()) { + AccessFlags entryAcc = entryIndex.getMethodAccess(entry); + if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { + for (EntryReference ref : referenceIndex.getReferencesToMethod(entry)) { + if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + } + } + + for (ClassEntry entry : entryIndex.getClasses()) { + AccessFlags entryAcc = entryIndex.getClassAccess(entry); + if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { + for (EntryReference ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { + if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + + for (EntryReference ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { + if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); + } + } + } + + for (ClassEntry parent : inheritanceIndex.getParents(entry)) { + AccessFlags parentAcc = entryIndex.getClassAccess(parent); + if (parentAcc != null && !parentAcc.isPublic() && !parentAcc.isPrivate()) { + addConnection(entry, parent); + } + } + + ClassEntry outerClass = entry.getOuterClass(); + if (outerClass != null) { + addConnection(entry, outerClass); + } + } + } + + private void addPartitions(EntryIndex entryIndex) { + Set unassignedClasses = Sets.newHashSet(entryIndex.getClasses()); + while (!unassignedClasses.isEmpty()) { + Iterator iterator = unassignedClasses.iterator(); + ClassEntry initialEntry = iterator.next(); + iterator.remove(); + + HashSet partition = Sets.newHashSet(); + partition.add(initialEntry); + buildPartition(unassignedClasses, partition, initialEntry); + partitions.add(partition); + for (ClassEntry entry : partition) { + classPartitions.put(entry, partition); + } + } + } + + public Collection> getPartitions() { + return partitions; + } + + public Set getPartition(ClassEntry classEntry) { + return classPartitions.get(classEntry); + } + + @Override + public void processIndex(JarIndex index) { + EntryIndex entryIndex = index.getEntryIndex(); + ReferenceIndex referenceIndex = index.getReferenceIndex(); + InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); + addConnections(entryIndex, referenceIndex, inheritanceIndex); + addPartitions(entryIndex); + } +} diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java index 2b63c5d4..6764ac0c 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java @@ -4,6 +4,8 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.MethodDescriptor; +import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.*; import java.util.Collection; @@ -15,6 +17,43 @@ public class ReferenceIndex implements JarIndexer { private Multimap> referencesToMethods = HashMultimap.create(); private Multimap> referencesToClasses = HashMultimap.create(); private Multimap> referencesToFields = HashMultimap.create(); + private Multimap> fieldTypeReferences = HashMultimap.create(); + private Multimap> methodTypeReferences = HashMultimap.create(); + + @Override + public void indexMethod(MethodDefEntry methodEntry) { + indexMethodDescriptor(methodEntry, methodEntry.getDesc()); + } + + private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) { + for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) { + indexMethodTypeDescriptor(entry, typeDescriptor); + } + indexMethodTypeDescriptor(entry, descriptor.getReturnDesc()); + } + + private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { + if (typeDescriptor.isType()) { + ClassEntry referencedClass = typeDescriptor.getTypeEntry(); + methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); + } else if (typeDescriptor.isArray()) { + indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); + } + } + + @Override + public void indexField(FieldDefEntry fieldEntry) { + indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc()); + } + + private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { + if (typeDescriptor.isType()) { + ClassEntry referencedClass = typeDescriptor.getTypeEntry(); + fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); + } else if (typeDescriptor.isArray()) { + indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); + } + } @Override public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { @@ -25,6 +64,8 @@ public class ReferenceIndex implements JarIndexer { ClassEntry referencedClass = referencedEntry.getParent(); referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry)); } + + indexMethodDescriptor(callerEntry, referencedEntry.getDesc()); } @Override @@ -38,10 +79,12 @@ public class ReferenceIndex implements JarIndexer { referencesToMethods = remapReferencesTo(index, referencesToMethods); referencesToClasses = remapReferencesTo(index, referencesToClasses); referencesToFields = remapReferencesTo(index, referencesToFields); + fieldTypeReferences = remapReferencesTo(index, fieldTypeReferences); + methodTypeReferences = remapReferencesTo(index, methodTypeReferences); } private , V extends Entry> Multimap remapReferences(JarIndex index, Multimap multimap) { - Multimap resolved = HashMultimap.create(); + Multimap resolved = HashMultimap.create(multimap.keySet().size(), multimap.size() / multimap.keySet().size()); for (Map.Entry entry : multimap.entries()) { resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); } @@ -49,7 +92,7 @@ public class ReferenceIndex implements JarIndexer { } private , C extends Entry> Multimap> remapReferencesTo(JarIndex index, Multimap> multimap) { - Multimap> resolved = HashMultimap.create(); + Multimap> resolved = HashMultimap.create(multimap.keySet().size(), multimap.size() / multimap.keySet().size()); for (Map.Entry> entry : multimap.entries()) { resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); } @@ -79,4 +122,12 @@ public class ReferenceIndex implements JarIndexer { public Collection> getReferencesToMethod(MethodEntry entry) { return referencesToMethods.get(entry); } + + public Collection> getFieldTypeReferencesToClass(ClassEntry entry) { + return fieldTypeReferences.get(entry); + } + + public Collection> getMethodTypeReferencesToClass(ClassEntry entry) { + return methodTypeReferences.get(entry); + } } -- cgit v1.2.3 From cb8823eb0b446d5c1b9b580e5578866e691771d8 Mon Sep 17 00:00:00 2001 From: liach Date: Wed, 15 May 2019 22:03:13 -0700 Subject: Feature/weave (#138) * Add weave/stitch style command system to enigma Also fixed divide by zero stupidity Signed-off-by: liach * Add tests for package access index and command Signed-off-by: liach * Minor tweaks Signed-off-by: liach --- src/main/java/cuchaz/enigma/CommandMain.java | 248 +++++---------------- .../analysis/index/PackageVisibilityIndex.java | 10 +- .../enigma/analysis/index/ReferenceIndex.java | 3 +- .../enigma/command/CheckMappingsCommand.java | 62 ++++++ src/main/java/cuchaz/enigma/command/Command.java | 140 ++++++++++++ .../enigma/command/ConvertMappingsCommand.java | 47 ++++ .../cuchaz/enigma/command/DecompileCommand.java | 33 +++ .../cuchaz/enigma/command/DeobfuscateCommand.java | 33 +++ 8 files changed, 374 insertions(+), 202 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java create mode 100644 src/main/java/cuchaz/enigma/command/Command.java create mode 100644 src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java create mode 100644 src/main/java/cuchaz/enigma/command/DecompileCommand.java create mode 100644 src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java (limited to 'src/main/java') diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index db4fd125..5b250872 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java @@ -11,43 +11,45 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.mapping.EntryMapping; -import cuchaz.enigma.translation.mapping.serde.MappingFormat; -import cuchaz.enigma.translation.mapping.tree.EntryTree; -import cuchaz.enigma.translation.representation.entry.ClassEntry; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import cuchaz.enigma.command.*; + +import java.util.LinkedHashMap; import java.util.Locale; -import java.util.Set; -import java.util.jar.JarFile; -import java.util.stream.Collectors; +import java.util.Map; public class CommandMain { - public static void main(String[] args) throws Exception { + private static final Map COMMANDS = new LinkedHashMap<>(); + + public static void main(String... args) throws Exception { try { // process the command - String command = getArg(args, 0, "command", true).toLowerCase(Locale.ROOT); - switch (command) { - case "deobfuscate": - deobfuscate(args); - break; - case "decompile": - decompile(args); - break; - case "convertmappings": - convertMappings(args); - break; - case "checkmappings": - checkMappings(args); - break; - default: - throw new IllegalArgumentException("Command not recognized: " + command); + if (args.length < 1) + throw new IllegalArgumentException("Requires a command"); + String command = args[0].toLowerCase(Locale.ROOT); + + Command cmd = COMMANDS.get(command); + if (cmd == null) + throw new IllegalArgumentException("Command not recognized: " + command); + + if (!cmd.isValidArgument(args.length - 1)) { + throw new CommandHelpException(cmd); + } + + String[] cmdArgs = new String[args.length - 1]; + System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); + + try { + cmd.run(cmdArgs); + } catch (Exception ex) { + throw new CommandHelpException(cmd, ex); } + } catch (CommandHelpException ex) { + System.err.println(ex.getMessage()); + System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); + System.out.println("Command " + ex.command.name + " has encountered an error! Usage:"); + printHelp(ex.command); + System.exit(1); } catch (IllegalArgumentException ex) { System.err.println(ex.getMessage()); printHelp(); @@ -60,187 +62,41 @@ public class CommandMain { System.out.println("Usage:"); System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain "); System.out.println("\twhere is one of:"); - System.out.println("\t\tdeobfuscate []"); - System.out.println("\t\tdecompile []"); - System.out.println("\t\tconvertmappings "); - System.out.println("\t\tcheckmappings "); - } - - private static void decompile(String[] args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); - File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)); - Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); - Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); - deobfuscator.writeSources(fileJarOut.toPath(), new ConsoleProgressListener()); - } - - private static void deobfuscate(String[] args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); - File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); - Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); - Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); - deobfuscator.writeTransformedJar(fileJarOut, new ConsoleProgressListener()); - } - private static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { - System.out.println("Reading jar..."); - Deobfuscator deobfuscator = new Deobfuscator(jar); - if (fileMappings != null) { - System.out.println("Reading mappings..."); - EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); - deobfuscator.setMappings(mappings); + for (Command command : COMMANDS.values()) { + printHelp(command); } - return deobfuscator; } - private static void convertMappings(String[] args) throws Exception { - Path fileMappings = getReadablePath(getArg(args, 1, "enigma mapping", true)); - File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); - String name = getArg(args, 3, "format desc", true); - MappingFormat saveFormat; - try { - saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT)); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException(name + "is not a valid mapping format!"); - } - - System.out.println("Reading mappings..."); - - MappingFormat readFormat = chooseEnigmaFormat(fileMappings); - EntryTree mappings = readFormat.read(fileMappings, new ConsoleProgressListener()); - System.out.println("Saving new mappings..."); - - saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); + private static void printHelp(Command command) { + System.out.println("\t\t" + command.name + " " + command.getUsage()); } - private static void checkMappings(String[] args) throws Exception { - File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); - Path fileMappings = getReadablePath(getArg(args, 2, "enigma mapping", true)); - - System.out.println("Reading JAR..."); - Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); - System.out.println("Reading mappings..."); - - MappingFormat format = chooseEnigmaFormat(fileMappings); - EntryTree mappings = format.read(fileMappings, ProgressListener.VOID); - deobfuscator.setMappings(mappings); - - JarIndex idx = deobfuscator.getJarIndex(); - - boolean error = false; - - for (Set partition : idx.getPackageVisibilityIndex().getPartitions()) { - long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); - if (packages > 1) { - error = true; - System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); - } - } - - if (error) { - throw new Exception("Access violations detected"); + private static void register(Command command) { + Command old = COMMANDS.put(command.name, command); + if (old != null) { + System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command); } } - private static MappingFormat chooseEnigmaFormat(Path path) { - if (Files.isDirectory(path)) { - return MappingFormat.ENIGMA_DIRECTORY; - } else { - return MappingFormat.ENIGMA_FILE; - } - } - - private static String getArg(String[] args, int i, String name, boolean required) { - if (i >= args.length) { - if (required) { - throw new IllegalArgumentException(name + " is required"); - } else { - return null; - } - } - return args[i]; - } - - private static File getWritableFile(String path) { - if (path == null) { - return null; - } - File file = new File(path).getAbsoluteFile(); - File dir = file.getParentFile(); - if (dir == null) { - throw new IllegalArgumentException("Cannot write file: " + path); - } - // quick fix to avoid stupid stuff in Gradle code - if (!dir.isDirectory()) { - dir.mkdirs(); - } - return file; - } - - private static File getWritableFolder(String path) { - if (path == null) { - return null; - } - File dir = new File(path).getAbsoluteFile(); - if (!dir.exists()) { - throw new IllegalArgumentException("Cannot write to folder: " + dir); - } - return dir; + static { + register(new DeobfuscateCommand()); + register(new DecompileCommand()); + register(new ConvertMappingsCommand()); + register(new CheckMappingsCommand()); } - private static File getReadableFile(String path) { - if (path == null) { - return null; - } - File file = new File(path).getAbsoluteFile(); - if (!file.exists()) { - throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); - } - return file; - } + private static final class CommandHelpException extends IllegalArgumentException { - private static Path getReadablePath(String path) { - if (path == null) { - return null; - } - Path file = Paths.get(path).toAbsolutePath(); - if (!Files.exists(file)) { - throw new IllegalArgumentException("Cannot find file: " + file.toString()); - } - return file; - } + final Command command; - public static class ConsoleProgressListener implements ProgressListener { - - private static final int ReportTime = 5000; // 5s - - private int totalWork; - private long startTime; - private long lastReportTime; - - @Override - public void init(int totalWork, String title) { - this.totalWork = totalWork; - this.startTime = System.currentTimeMillis(); - this.lastReportTime = this.startTime; - System.out.println(title); + CommandHelpException(Command command) { + this.command = command; } - @Override - public void step(int numDone, String message) { - long now = System.currentTimeMillis(); - boolean isLastUpdate = numDone == this.totalWork; - boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; - - if (shouldReport) { - int percent = numDone * 100 / this.totalWork; - System.out.println(String.format("\tProgress: %3d%%", percent)); - this.lastReportTime = now; - } - if (isLastUpdate) { - double elapsedSeconds = (now - this.startTime) / 1000.0; - System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); - } + CommandHelpException(Command command, Throwable cause) { + super(cause); + this.command = command; } } } diff --git a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java index 9e9115fe..da28ac41 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java @@ -11,7 +11,7 @@ import cuchaz.enigma.translation.representation.entry.*; import java.util.*; public class PackageVisibilityIndex implements JarIndexer { - private static boolean isPackageVisibleOnlyRef(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { + private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { if (entryAcc.isPublic()) return false; if (entryAcc.isProtected()) { Set callerAncestors = inheritanceIndex.getAncestors(ref.context.getContainingClass()); @@ -43,7 +43,7 @@ public class PackageVisibilityIndex implements JarIndexer { AccessFlags entryAcc = entryIndex.getFieldAccess(entry); if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { for (EntryReference ref : referenceIndex.getReferencesToField(entry)) { - if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); } } @@ -54,7 +54,7 @@ public class PackageVisibilityIndex implements JarIndexer { AccessFlags entryAcc = entryIndex.getMethodAccess(entry); if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { for (EntryReference ref : referenceIndex.getReferencesToMethod(entry)) { - if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); } } @@ -65,13 +65,13 @@ public class PackageVisibilityIndex implements JarIndexer { AccessFlags entryAcc = entryIndex.getClassAccess(entry); if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { for (EntryReference ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { - if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); } } for (EntryReference ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { - if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { + if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); } } diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java index 6764ac0c..04306bd9 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java @@ -92,7 +92,8 @@ public class ReferenceIndex implements JarIndexer { } private , C extends Entry> Multimap> remapReferencesTo(JarIndex index, Multimap> multimap) { - Multimap> resolved = HashMultimap.create(multimap.keySet().size(), multimap.size() / multimap.keySet().size()); + final int keySetSize = multimap.keySet().size(); + Multimap> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); for (Map.Entry> entry : multimap.entries()) { resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); } diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java new file mode 100644 index 00000000..7ec7679c --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java @@ -0,0 +1,62 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.io.File; +import java.nio.file.Path; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +public class CheckMappingsCommand extends Command { + + public CheckMappingsCommand() { + super("checkmappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2; + } + + @Override + public void run(String... args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); + Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); + + System.out.println("Reading JAR..."); + Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); + System.out.println("Reading mappings..."); + + MappingFormat format = chooseEnigmaFormat(fileMappings); + EntryTree mappings = format.read(fileMappings, ProgressListener.VOID); + deobfuscator.setMappings(mappings); + + JarIndex idx = deobfuscator.getJarIndex(); + + boolean error = false; + + for (Set partition : idx.getPackageVisibilityIndex().getPartitions()) { + long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); + if (packages > 1) { + error = true; + System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); + } + } + + if (error) { + throw new IllegalStateException("Errors in package visibility detected, see SysErr above"); + } + } +} diff --git a/src/main/java/cuchaz/enigma/command/Command.java b/src/main/java/cuchaz/enigma/command/Command.java new file mode 100644 index 00000000..b107fb61 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/Command.java @@ -0,0 +1,140 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.JarFile; + +public abstract class Command { + public final String name; + + protected Command(String name) { + this.name = name; + } + + public abstract String getUsage(); + + public abstract boolean isValidArgument(int length); + + public abstract void run(String... args) throws Exception; + + protected static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { + System.out.println("Reading jar..."); + Deobfuscator deobfuscator = new Deobfuscator(jar); + if (fileMappings != null) { + System.out.println("Reading mappings..."); + EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); + deobfuscator.setMappings(mappings); + } + return deobfuscator; + } + + protected static MappingFormat chooseEnigmaFormat(Path path) { + if (Files.isDirectory(path)) { + return MappingFormat.ENIGMA_DIRECTORY; + } else { + return MappingFormat.ENIGMA_FILE; + } + } + + protected static File getWritableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + File dir = file.getParentFile(); + if (dir == null) { + throw new IllegalArgumentException("Cannot write file: " + path); + } + // quick fix to avoid stupid stuff in Gradle code + if (!dir.isDirectory()) { + dir.mkdirs(); + } + return file; + } + + protected static File getWritableFolder(String path) { + if (path == null) { + return null; + } + File dir = new File(path).getAbsoluteFile(); + if (!dir.exists()) { + throw new IllegalArgumentException("Cannot write to folder: " + dir); + } + return dir; + } + + protected static File getReadableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + if (!file.exists()) { + throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); + } + return file; + } + + protected static Path getReadablePath(String path) { + if (path == null) { + return null; + } + Path file = Paths.get(path).toAbsolutePath(); + if (!Files.exists(file)) { + throw new IllegalArgumentException("Cannot find file: " + file.toString()); + } + return file; + } + + protected static String getArg(String[] args, int i, String name, boolean required) { + if (i >= args.length) { + if (required) { + throw new IllegalArgumentException(name + " is required"); + } else { + return null; + } + } + return args[i]; + } + + public static class ConsoleProgressListener implements ProgressListener { + + private static final int ReportTime = 5000; // 5s + + private int totalWork; + private long startTime; + private long lastReportTime; + + @Override + public void init(int totalWork, String title) { + this.totalWork = totalWork; + this.startTime = System.currentTimeMillis(); + this.lastReportTime = this.startTime; + System.out.println(title); + } + + @Override + public void step(int numDone, String message) { + long now = System.currentTimeMillis(); + boolean isLastUpdate = numDone == this.totalWork; + boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; + + if (shouldReport) { + int percent = numDone * 100 / this.totalWork; + System.out.println(String.format("\tProgress: %3d%%", percent)); + this.lastReportTime = now; + } + if (isLastUpdate) { + double elapsedSeconds = (now - this.startTime) / 1000.0; + System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java new file mode 100644 index 00000000..75d3791d --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java @@ -0,0 +1,47 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.File; +import java.nio.file.Path; +import java.util.Locale; + +public class ConvertMappingsCommand extends Command { + + public ConvertMappingsCommand() { + super("convertmappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 3; + } + + @Override + public void run(String... args) throws Exception { + Path fileMappings = getReadablePath(getArg(args, 0, "enigma mappings", true)); + File result = getWritableFile(getArg(args, 1, "converted mappings", true)); + String name = getArg(args, 2, "format desc", true); + MappingFormat saveFormat; + try { + saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(name + "is not a valid mapping format!"); + } + + System.out.println("Reading mappings..."); + + MappingFormat readFormat = chooseEnigmaFormat(fileMappings); + EntryTree mappings = readFormat.read(fileMappings, new ConsoleProgressListener()); + System.out.println("Saving new mappings..."); + + saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); + } +} diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java new file mode 100644 index 00000000..a58d9085 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/DecompileCommand.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; + +import java.io.File; +import java.nio.file.Path; +import java.util.jar.JarFile; + +public class DecompileCommand extends Command { + + public DecompileCommand() { + super("decompile"); + } + + @Override + public String getUsage() { + return " []"; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2 || length == 3; + } + + @Override + public void run(String... args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); + File fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)); + Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeSources(fileJarOut.toPath(), new Command.ConsoleProgressListener()); + } +} diff --git a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java new file mode 100644 index 00000000..5d499385 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; + +import java.io.File; +import java.nio.file.Path; +import java.util.jar.JarFile; + +public class DeobfuscateCommand extends Command { + + public DeobfuscateCommand() { + super("deobfuscate"); + } + + @Override + public String getUsage() { + return " []"; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2 || length == 3; + } + + @Override + public void run(String... args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); + File fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)); + Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeTransformedJar(fileJarOut, new Command.ConsoleProgressListener()); + } +} -- cgit v1.2.3 From 463aee1dfc1222e8f6f0312aa46fb9b9dcfa13a1 Mon Sep 17 00:00:00 2001 From: Erlend Åmdal Date: Sat, 18 May 2019 11:13:24 +0200 Subject: Method type reference corrections (#142) * Add more specific returns for translatables * Only index method descriptors for implemented methods and methods in generated lambda classes --- .../analysis/index/IndexReferenceVisitor.java | 61 ++++++------ .../cuchaz/enigma/analysis/index/JarIndex.java | 10 ++ .../cuchaz/enigma/analysis/index/JarIndexer.java | 4 + .../enigma/analysis/index/ReferenceIndex.java | 16 +++- .../enigma/translation/representation/Lambda.java | 105 +++++++++++++++++++++ .../representation/MethodDescriptor.java | 2 +- .../representation/entry/ParentedEntry.java | 2 +- 7 files changed, 167 insertions(+), 33 deletions(-) create mode 100644 src/main/java/cuchaz/enigma/translation/representation/Lambda.java (limited to 'src/main/java') diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java index ba5d3b6a..b730a8a1 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java @@ -1,16 +1,11 @@ package cuchaz.enigma.analysis.index; import cuchaz.enigma.translation.representation.AccessFlags; +import cuchaz.enigma.translation.representation.Lambda; import cuchaz.enigma.translation.representation.MethodDescriptor; import cuchaz.enigma.translation.representation.Signature; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import cuchaz.enigma.translation.representation.entry.FieldEntry; -import cuchaz.enigma.translation.representation.entry.MethodDefEntry; -import cuchaz.enigma.translation.representation.entry.MethodEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Handle; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; +import cuchaz.enigma.translation.representation.entry.*; +import org.objectweb.asm.*; public class IndexReferenceVisitor extends ClassVisitor { private final JarIndexer indexer; @@ -54,29 +49,37 @@ public class IndexReferenceVisitor extends ClassVisitor { this.indexer.indexMethodReference(callerEntry, methodEntry); } + private static ParentedEntry getHandleEntry(Handle handle) { + switch (handle.getTag()) { + case Opcodes.H_GETFIELD: + case Opcodes.H_GETSTATIC: + case Opcodes.H_PUTFIELD: + case Opcodes.H_PUTSTATIC: + return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + case Opcodes.H_INVOKEINTERFACE: + case Opcodes.H_INVOKESPECIAL: + case Opcodes.H_INVOKESTATIC: + case Opcodes.H_INVOKEVIRTUAL: + case Opcodes.H_NEWINVOKESPECIAL: + return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); + } + throw new RuntimeException("Invalid handle tag " + handle.getTag()); + } + @Override public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { - for (Object bsmArg : bsmArgs) { - if (bsmArg instanceof Handle) { - Handle handle = (Handle) bsmArg; - switch (handle.getTag()) { - case Opcodes.H_GETFIELD: - case Opcodes.H_GETSTATIC: - case Opcodes.H_PUTFIELD: - case Opcodes.H_PUTSTATIC: - FieldEntry fieldEntry = FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); - this.indexer.indexFieldReference(callerEntry, fieldEntry); - break; - case Opcodes.H_INVOKEINTERFACE: - case Opcodes.H_INVOKESPECIAL: - case Opcodes.H_INVOKESTATIC: - case Opcodes.H_INVOKEVIRTUAL: - case Opcodes.H_NEWINVOKESPECIAL: - MethodEntry methodEntry = MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); - this.indexer.indexMethodReference(callerEntry, methodEntry); - break; - } - } + if ("java/lang/invoke/LambdaMetafactory".equals(bsm.getOwner()) && "metafactory".equals(bsm.getName())) { + Type samMethodType = (Type) bsmArgs[0]; + Handle implMethod = (Handle) bsmArgs[1]; + Type instantiatedMethodType = (Type) bsmArgs[2]; + + this.indexer.indexLambda(callerEntry, new Lambda( + name, + new MethodDescriptor(desc), + new MethodDescriptor(samMethodType.getDescriptor()), + getHandleEntry(implMethod), + new MethodDescriptor(instantiatedMethodType.getDescriptor()) + )); } } } diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java index ac907af0..fd4e618b 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java @@ -16,6 +16,7 @@ import com.google.common.collect.Multimap; import cuchaz.enigma.analysis.ParsedJar; import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.IndexEntryResolver; +import cuchaz.enigma.translation.representation.Lambda; import cuchaz.enigma.translation.representation.entry.*; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; @@ -129,6 +130,15 @@ public class JarIndex implements JarIndexer { indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry)); } + @Override + public void indexLambda(MethodDefEntry callerEntry, Lambda lambda) { + if (callerEntry.getParent().isJre()) { + return; + } + + indexers.forEach(indexer -> indexer.indexLambda(callerEntry, lambda)); + } + public EntryIndex getEntryIndex() { return entryIndex; } diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java index 457c2237..4f885b39 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java @@ -1,5 +1,6 @@ package cuchaz.enigma.analysis.index; +import cuchaz.enigma.translation.representation.Lambda; import cuchaz.enigma.translation.representation.entry.*; public interface JarIndexer { @@ -18,6 +19,9 @@ public interface JarIndexer { default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { } + default void indexLambda(MethodDefEntry callerEntry, Lambda lambda) { + } + default void processIndex(JarIndex index) { } } diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java index 04306bd9..f54c90dd 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java @@ -4,6 +4,7 @@ import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.Lambda; import cuchaz.enigma.translation.representation.MethodDescriptor; import cuchaz.enigma.translation.representation.TypeDescriptor; import cuchaz.enigma.translation.representation.entry.*; @@ -64,8 +65,6 @@ public class ReferenceIndex implements JarIndexer { ClassEntry referencedClass = referencedEntry.getParent(); referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry)); } - - indexMethodDescriptor(callerEntry, referencedEntry.getDesc()); } @Override @@ -73,6 +72,19 @@ public class ReferenceIndex implements JarIndexer { referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry)); } + @Override + public void indexLambda(MethodDefEntry callerEntry, Lambda lambda) { + if (lambda.getImplMethod() instanceof MethodEntry) { + indexMethodReference(callerEntry, (MethodEntry) lambda.getImplMethod()); + } else { + indexFieldReference(callerEntry, (FieldEntry) lambda.getImplMethod()); + } + + indexMethodDescriptor(callerEntry, lambda.getInvokedType()); + indexMethodDescriptor(callerEntry, lambda.getSamMethodType()); + indexMethodDescriptor(callerEntry, lambda.getInstantiatedMethodType()); + } + @Override public void processIndex(JarIndex index) { methodReferences = remapReferences(index, methodReferences); diff --git a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/src/main/java/cuchaz/enigma/translation/representation/Lambda.java new file mode 100644 index 00000000..63eb5630 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/Lambda.java @@ -0,0 +1,105 @@ +package cuchaz.enigma.translation.representation; + +import cuchaz.enigma.translation.Translatable; +import cuchaz.enigma.translation.Translator; +import cuchaz.enigma.translation.mapping.EntryMap; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryResolver; +import cuchaz.enigma.translation.mapping.ResolutionStrategy; +import cuchaz.enigma.translation.representation.entry.ClassEntry; +import cuchaz.enigma.translation.representation.entry.MethodEntry; +import cuchaz.enigma.translation.representation.entry.ParentedEntry; + +import java.util.Objects; + +public class Lambda implements Translatable { + private final String invokedName; + private final MethodDescriptor invokedType; + private final MethodDescriptor samMethodType; + private final ParentedEntry implMethod; + private final MethodDescriptor instantiatedMethodType; + + public Lambda(String invokedName, MethodDescriptor invokedType, MethodDescriptor samMethodType, ParentedEntry implMethod, MethodDescriptor instantiatedMethodType) { + this.invokedName = invokedName; + this.invokedType = invokedType; + this.samMethodType = samMethodType; + this.implMethod = implMethod; + this.instantiatedMethodType = instantiatedMethodType; + } + + @Override + public Lambda translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + MethodEntry samMethod = new MethodEntry(getInterface(), invokedName, samMethodType); + EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod); + + return new Lambda( + samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName, + invokedType.translate(translator, resolver, mappings), + samMethodType.translate(translator, resolver, mappings), + implMethod.translate(translator, resolver, mappings), + instantiatedMethodType.translate(translator, resolver, mappings) + ); + } + + private EntryMapping resolveMapping(EntryResolver resolver, EntryMap mappings, MethodEntry methodEntry) { + for (MethodEntry entry : resolver.resolveEntry(methodEntry, ResolutionStrategy.RESOLVE_ROOT)) { + EntryMapping mapping = mappings.get(entry); + if (mapping != null) { + return mapping; + } + } + return null; + } + + public ClassEntry getInterface() { + return invokedType.getReturnDesc().getTypeEntry(); + } + + public String getInvokedName() { + return invokedName; + } + + public MethodDescriptor getInvokedType() { + return invokedType; + } + + public MethodDescriptor getSamMethodType() { + return samMethodType; + } + + public ParentedEntry getImplMethod() { + return implMethod; + } + + public MethodDescriptor getInstantiatedMethodType() { + return instantiatedMethodType; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Lambda lambda = (Lambda) o; + return Objects.equals(invokedName, lambda.invokedName) && + Objects.equals(invokedType, lambda.invokedType) && + Objects.equals(samMethodType, lambda.samMethodType) && + Objects.equals(implMethod, lambda.implMethod) && + Objects.equals(instantiatedMethodType, lambda.instantiatedMethodType); + } + + @Override + public int hashCode() { + return Objects.hash(invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); + } + + @Override + public String toString() { + return "Lambda{" + + "invokedName='" + invokedName + '\'' + + ", invokedType=" + invokedType + + ", samMethodType=" + samMethodType + + ", implMethod=" + implMethod + + ", instantiatedMethodType=" + instantiatedMethodType + + '}'; + } +} diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java index c59751f9..37a70148 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java +++ b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java @@ -118,7 +118,7 @@ public class MethodDescriptor implements Translatable { } @Override - public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + public MethodDescriptor translate(Translator translator, EntryResolver resolver, EntryMap mappings) { List translatedArguments = new ArrayList<>(argumentDescs.size()); for (TypeDescriptor argument : argumentDescs) { translatedArguments.add(translator.translate(argument)); diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java index 834da8de..b753d3a3 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java @@ -52,7 +52,7 @@ public abstract class ParentedEntry

> implements Entry

{ } @Override - public Translatable translate(Translator translator, EntryResolver resolver, EntryMap mappings) { + public ParentedEntry

translate(Translator translator, EntryResolver resolver, EntryMap mappings) { P parent = getParent(); EntryMapping mapping = resolveMapping(resolver, mappings); if (parent == null) { -- cgit v1.2.3