From 3008489eb2e6d9d75366893b7b5a5bfdb195a2ba Mon Sep 17 00:00:00 2001 From: JoJoDeveloping Date: Mon, 26 Aug 2019 15:12:19 +0200 Subject: [PATCH 1/3] Add general parameter naming --- .../oceanlabs/mcp/mcinjector/MCInjector.java | 31 +++++++--- .../mcp/mcinjector/MCInjectorImpl.java | 13 ++-- .../mcp/mcinjector/adaptors/ApplyMap.java | 27 +++++++-- .../{Constructors.java => Parameters.java} | 60 +++++++++++++------ 4 files changed, 98 insertions(+), 33 deletions(-) rename src/main/java/de/oceanlabs/mcp/mcinjector/data/{Constructors.java => Parameters.java} (50%) diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java index f98c16a..67a4c4c 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java @@ -30,7 +30,8 @@ public class MCInjector private Path fileIn, fileOut; private Path excIn, excOut; private Path accIn, accOut; - private Path ctrIn, ctrOut; + private Path ctrIn; + private Path prmIn, prmOut; private LVTNaming lvt; public MCInjector(Path fileIn, Path fileOut) @@ -104,9 +105,15 @@ public MCInjector constructors(Path ctrs) return this; } - public MCInjector constructorsOut(Path out) + public MCInjector parameters(Path ctrs) { - this.ctrOut = out; + this.prmIn = ctrs; + return this; + } + + public MCInjector parametersOut(Path out) + { + this.prmOut = out; return this; } @@ -121,8 +128,9 @@ public void process() throws IOException { MCInjectorImpl.process(fileIn, fileOut, accIn, accOut, - ctrIn, ctrOut, + ctrIn, excIn, excOut, + prmIn, prmOut, lvt); } @@ -174,7 +182,9 @@ public static void main(String[] args) throws Exception OptionSpec acc = parser.accepts("acc") .withRequiredArg().withValuesConvertedBy(PATH_ARG); OptionSpec accOut = parser.accepts("accOut").withRequiredArg().withValuesConvertedBy(PATH_ARG); OptionSpec ctr = parser.accepts("ctr") .withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec ctrOut = parser.accepts("ctrOut").withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec ctrOut = parser.accepts("ctrOut").withRequiredArg().withValuesConvertedBy(PATH_ARG).describedAs("legacy, can't be used anymore"); + OptionSpec prm = parser.accepts("prm") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec prmOut = parser.accepts("prmOut").withRequiredArg().withValuesConvertedBy(PATH_ARG); OptionSpec logLvl = parser.accepts("level") .withRequiredArg().withValuesConvertedBy(LEVEL_ARG).defaultsTo(Level.INFO); OptionSpec lvt = parser.accepts("lvt").withRequiredArg().ofType(LVTNaming.class).defaultsTo(LVTNaming.STRIP); @@ -192,6 +202,11 @@ else if (o.has(ver)) System.out.println(VERSION); return; } + else if (o.has(ctrOut)) + { + System.out.println("ctrOut is using the legacy format and is no longer supported!"); + return; + } MCInjector.LOG.setUseParentHandlers(false); MCInjector.LOG.setLevel(o.valueOf(logLvl)); @@ -205,7 +220,8 @@ else if (o.has(ver)) LOG.info("Access: " + o.valueOf(acc)); LOG.info(" " + o.valueOf(accOut)); LOG.info("Constructors: " + o.valueOf(ctr)); - LOG.info(" " + o.valueOf(ctrOut)); + LOG.info("Extra Params: " + o.valueOf(prm)); + LOG.info(" " + o.valueOf(prmOut)); LOG.info("LVT: " + o.valueOf(lvt)); try @@ -219,7 +235,8 @@ else if (o.has(ver)) .access(o.valueOf(acc)) .accessOut(o.valueOf(accOut)) .constructors(o.valueOf(ctr)) - .constructorsOut(o.valueOf(ctrOut)) + .parameters(o.valueOf(prm)) + .parametersOut(o.valueOf(prmOut)) .process(); } catch (Exception e) diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java index c415ca5..ee1edb8 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java @@ -32,7 +32,7 @@ import de.oceanlabs.mcp.mcinjector.adaptors.InnerClassInitAdder; import de.oceanlabs.mcp.mcinjector.adaptors.ParameterAnnotationFixer; import de.oceanlabs.mcp.mcinjector.data.Access; -import de.oceanlabs.mcp.mcinjector.data.Constructors; +import de.oceanlabs.mcp.mcinjector.data.Parameters; import de.oceanlabs.mcp.mcinjector.data.Exceptions; import de.oceanlabs.mcp.mcinjector.lvt.LVTFernflower; import de.oceanlabs.mcp.mcinjector.lvt.LVTLvt; @@ -47,15 +47,18 @@ public class MCInjectorImpl static void process( Path in, Path out, Path accIn, Path accOut, - Path ctrIn, Path ctrOut, + Path ctrIn, Path excIn, Path excOut, + Path prmIn, Path prmOut, LVTNaming naming) throws IOException { if (accIn != null) Access.INSTANCE.load(accIn); + if (prmIn != null) + Parameters.INSTANCE.load(prmIn); if (ctrIn != null) - Constructors.INSTANCE.load(ctrIn); + Parameters.INSTANCE.loadLegacy(ctrIn); if (excIn != null) Exceptions.INSTANCE.load(excIn); @@ -69,8 +72,8 @@ static void process( if (accOut != null) Access.INSTANCE.dump(accOut); - if (ctrOut != null) - Constructors.INSTANCE.dump(ctrOut); + if (prmOut != null) + Parameters.INSTANCE.dump(prmOut); if (excOut != null) Exceptions.INSTANCE.dump(excOut); diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java b/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java index 7e166ec..cfd40b5 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java @@ -10,6 +10,7 @@ import java.util.Set; import java.util.logging.Level; +import de.oceanlabs.mcp.mcinjector.data.Parameters; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -21,12 +22,12 @@ import de.oceanlabs.mcp.mcinjector.MCInjector; import de.oceanlabs.mcp.mcinjector.MCInjectorImpl; -import de.oceanlabs.mcp.mcinjector.data.Constructors; import de.oceanlabs.mcp.mcinjector.data.Exceptions; public class ApplyMap extends ClassVisitor { String className; + boolean isEnum; MCInjectorImpl injector; public ApplyMap(MCInjectorImpl injector, ClassVisitor cn) @@ -39,6 +40,7 @@ public ApplyMap(MCInjectorImpl injector, ClassVisitor cn) public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.className = name; + isEnum = (access & Opcodes.ACC_ENUM) != 0; super.visit(version, access, name, signature, superName, interfaces); } @@ -95,9 +97,11 @@ private void processLVT(String cls, String name, String desc, MethodNode mn) { List params = new ArrayList<>(); List types = new ArrayList<>(); + boolean isStatic = true; if ((mn.access & Opcodes.ACC_STATIC) == 0) { + isStatic = false; types.add(Type.getType("L" + cls + ";")); params.add(0, "this"); } @@ -109,11 +113,12 @@ private void processLVT(String cls, String name, String desc, MethodNode mn) return; MCInjector.LOG.fine(" Generating map:"); - String nameFormat = "p_" + name + "_%d_"; + String nameFormat = null; if (name.matches("func_\\d+_.+")) // A srg name method params are just p_MethodID_ParamIndex_ nameFormat = "p_" + name.substring(5, name.indexOf('_', 5)) + "_%s_"; - else if (name.equals("")) // Every constructor is given a unique ID, try to load the ID from the map, if none is found assign a new one - nameFormat = "p_i" + Constructors.INSTANCE.getID(className, desc, types.size() > 1) + "_%s_"; + else if(!isSynthetic(mn)) + nameFormat = "p_" + Parameters.INSTANCE.getName(className, name, desc, types.size() > params.size(), isStatic) + "_%s_"; //assign new name only if there are names remaining + else nameFormat = "p_%s_"; //don't really care about synthetics for (int x = params.size(), y = x; x < types.size(); x++) { @@ -190,4 +195,18 @@ else if (tmp.getType() != AbstractInsnNode.LABEL) Collections.sort(mn.localVariables, (o1, o2) -> o1.index < o2.index ? -1 : (o1.index == o2.index ? 0 : 1)); } + + private boolean isSynthetic(MethodNode mn){ + if ((mn.access & Opcodes.ACC_SYNTHETIC) != 0) return true; + + //check for special case pursuant to JLS 13.1.7 + //which specifies that these are the one and only proper methods that may be generated and not be marked synthetic + if (isEnum && (mn.access & Opcodes.ACC_STATIC) != 0) { + if ("valueOf".equals(mn.name) && mn.desc.equals("(Ljava/lang/String;)L" + className + ";")) + return true; + if("values".equals(mn.name) && mn.desc.equals("()[L" + className + ";")) + return true; + } + return false; + } } diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Constructors.java b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java similarity index 50% rename from src/main/java/de/oceanlabs/mcp/mcinjector/data/Constructors.java rename to src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java index 2ea2901..94cf7e5 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Constructors.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java @@ -1,5 +1,7 @@ package de.oceanlabs.mcp.mcinjector.data; +import de.oceanlabs.mcp.mcinjector.MCInjector; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -10,9 +12,7 @@ import java.util.Map; import java.util.stream.Collectors; -import de.oceanlabs.mcp.mcinjector.MCInjector; - -public enum Constructors +public enum Parameters { INSTANCE; @@ -26,7 +26,7 @@ public boolean load(Path file) this.fromID.clear(); try { - MCInjector.LOG.fine("Loading Constructors from: " + file); + MCInjector.LOG.fine("Loading Parameters from: " + file); Files.readAllLines(file).forEach(line -> { line = line.trim(); @@ -35,19 +35,44 @@ public boolean load(Path file) String[] parts = line.split(" " ); int id = Integer.parseInt(parts[0]); - MCInjector.LOG.fine(" Constructor ID loaded " + id + " " + parts[0] + " " + parts[1]); - this.setID(parts[1], parts[2], id); + MCInjector.LOG.fine(" Method parameter ID loaded " + id + " " + parts[0] + " " + parts[1] + " " + parts[3]); + this.setName(parts[1], parts[2], parts[3], id); }); } catch (IOException e) { e.printStackTrace(); - MCInjector.LOG.warning("Could not load Constructors list: " + e.toString()); + MCInjector.LOG.warning("Could not load Parameters list: " + e.toString()); return false; } return true; } + public boolean loadLegacy(Path file) + { + try { + MCInjector.LOG.fine("Loading Constructors from: " + file); + Files.readAllLines(file).forEach(line -> + { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#")) + return; + + String[] parts = line.split(" " ); + int id = Integer.parseInt(parts[0]); + MCInjector.LOG.fine(" Legacy Constructor ID loaded " + id + " " + parts[0] + " " + parts[1]); + this.setName(parts[1], "", parts[2], id); + }); + return true; + } + catch (IOException e) + { + e.printStackTrace(); + MCInjector.LOG.warning("Could not import Constructors list: " + e.toString()); + return false; + } + } + public boolean dump(Path file) { try @@ -61,31 +86,32 @@ public boolean dump(Path file) catch (IOException e) { e.printStackTrace(); - MCInjector.LOG.warning("Could not dump Constructors list: " + e.toString()); + MCInjector.LOG.warning("Could not dump Parameters list: " + e.toString()); return false; } return true; } - public void setID(String cls, String desc, int id) + public void setName(String cls, String method, String desc, int id) { if (id < 0) throw new IllegalArgumentException("ID must be positive: " + id); this.maxID = Math.max(this.maxID, id); - this.fromDesc.put(cls + " " + desc, id); - this.fromID .put(id, cls + " " + desc); + this.fromDesc.put(cls + " " + method + " " + desc, id); + this.fromID .put(id, cls + " " + method + " " + desc); } - public int getID(String cls, String desc, boolean generate) + public String getName(String cls, String method, String desc, boolean generate, boolean isStatic) { - Integer id = this.fromDesc.get(cls + " " + desc); + Integer id = this.fromDesc.get(cls + " " + method + " " + desc); if (id == null) { if (!generate) - return -1; - id = ++maxID; - this.setID(cls, desc, id); + return method; //if we are not generating new names we will return the old parameter format, _p_funcname_x_ + int newId = ++maxID; + this.setName(cls, method, desc, newId); + return Integer.toString(newId); } - return id; + return Integer.toString(id); } } From 59db17dff011aaef1c59de28fb3a1dd77b78b3cc Mon Sep 17 00:00:00 2001 From: JoJoDeveloping Date: Tue, 28 Jan 2020 03:44:12 +0100 Subject: [PATCH 2/3] Add basic inheritance walking, modify paramter format --- .../oceanlabs/mcp/mcinjector/MCInjector.java | 66 ++++++++--- .../mcp/mcinjector/MCInjectorImpl.java | 6 +- .../mcp/mcinjector/adaptors/ApplyMap.java | 16 ++- .../mcp/mcinjector/data/Classpath.java | 106 ++++++++++++++++++ .../mcp/mcinjector/data/Parameters.java | 23 +++- 5 files changed, 195 insertions(+), 22 deletions(-) create mode 100644 src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java index 67a4c4c..b1158b4 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java @@ -1,12 +1,15 @@ package de.oceanlabs.mcp.mcinjector; import static joptsimple.internal.Reflection.invoke; +import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.Method; import java.nio.file.FileSystem; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; import java.util.Locale; import java.util.Optional; import java.util.logging.FileHandler; @@ -14,6 +17,7 @@ import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; +import java.util.stream.Collectors; import de.oceanlabs.mcp.mcinjector.lvt.LVTNaming; import joptsimple.OptionException; @@ -32,6 +36,7 @@ public class MCInjector private Path accIn, accOut; private Path ctrIn; private Path prmIn, prmOut; + private Path[] classpath; private LVTNaming lvt; public MCInjector(Path fileIn, Path fileOut) @@ -75,6 +80,11 @@ public void publish(LogRecord record) return this; } + private MCInjector classpath(Path[] clsPath) { + this.classpath = clsPath; + return this; + } + public MCInjector exceptions(Path exc) { this.excIn = exc; @@ -131,7 +141,8 @@ public void process() throws IOException ctrIn, excIn, excOut, prmIn, prmOut, - lvt); + lvt, + classpath); } private static ValueConverter PATH_ARG = new ValueConverter() @@ -151,6 +162,23 @@ public String valuePattern() return null; } }; + private static ValueConverter MULTIPATH_ARG = new ValueConverter() + { + public Path[] convert(String value ) + { + return Arrays.stream(value.split(File.pathSeparator)).map(Paths::get).toArray(Path[]::new); + } + + public Class valueType() + { + return Path[].class; + } + + public String valuePattern() + { + return null; + } + }; private static ValueConverter LEVEL_ARG = new ValueConverter() { public Level convert( String value ) @@ -172,21 +200,22 @@ public String valuePattern() public static void main(String[] args) throws Exception { OptionParser parser = new OptionParser(); - OptionSpec help = parser.accepts("help") .forHelp(); - OptionSpec ver = parser.accepts("version").forHelp(); - OptionSpec in = parser.accepts("in") .withRequiredArg().withValuesConvertedBy(PATH_ARG).required(); - OptionSpec out = parser.accepts("out") .withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec log = parser.accepts("log") .withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec exc = parser.accepts("exc") .withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec excOut = parser.accepts("excOut").withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec acc = parser.accepts("acc") .withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec accOut = parser.accepts("accOut").withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec ctr = parser.accepts("ctr") .withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec ctrOut = parser.accepts("ctrOut").withRequiredArg().withValuesConvertedBy(PATH_ARG).describedAs("legacy, can't be used anymore"); - OptionSpec prm = parser.accepts("prm") .withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec prmOut = parser.accepts("prmOut").withRequiredArg().withValuesConvertedBy(PATH_ARG); - OptionSpec logLvl = parser.accepts("level") .withRequiredArg().withValuesConvertedBy(LEVEL_ARG).defaultsTo(Level.INFO); - OptionSpec lvt = parser.accepts("lvt").withRequiredArg().ofType(LVTNaming.class).defaultsTo(LVTNaming.STRIP); + OptionSpec help = parser.accepts("help") .forHelp(); + OptionSpec ver = parser.accepts("version") .forHelp(); + OptionSpec in = parser.accepts("in") .withRequiredArg().withValuesConvertedBy(PATH_ARG).required(); + OptionSpec out = parser.accepts("out") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec log = parser.accepts("log") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec exc = parser.accepts("exc") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec excOut = parser.accepts("excOut") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec acc = parser.accepts("acc") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec accOut = parser.accepts("accOut") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec ctr = parser.accepts("ctr") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec ctrOut = parser.accepts("ctrOut") .withRequiredArg().withValuesConvertedBy(PATH_ARG).describedAs("legacy, can't be used anymore"); + OptionSpec prm = parser.accepts("prm") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec prmOut = parser.accepts("prmOut") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec logLvl = parser.accepts("level") .withRequiredArg().withValuesConvertedBy(LEVEL_ARG).defaultsTo(Level.INFO); + OptionSpec lvt = parser.accepts("lvt") .withRequiredArg().ofType(LVTNaming.class).defaultsTo(LVTNaming.STRIP); + OptionSpec clsPath = parser.acceptsAll(Arrays.stream(new String[]{"classpath", "e"}).collect(Collectors.toList())).withRequiredArg().withValuesConvertedBy(MULTIPATH_ARG); try { @@ -223,6 +252,10 @@ else if (o.has(ctrOut)) LOG.info("Extra Params: " + o.valueOf(prm)); LOG.info(" " + o.valueOf(prmOut)); LOG.info("LVT: " + o.valueOf(lvt)); + if (o.hasArgument(clsPath)) + for(Path[] pp : o.valuesOf(clsPath)) + for(Path p : pp) + LOG.info("Classpath: " + p); try { @@ -230,6 +263,7 @@ else if (o.has(ctrOut)) .log() .lvt(o.valueOf(lvt)) .log(o.valueOf(log)) + .classpath(o.valuesOf(clsPath).stream().flatMap(Arrays::stream).toArray(Path[]::new)) .exceptions(o.valueOf(exc)) .exceptionsOut(o.valueOf(excOut)) .access(o.valueOf(acc)) diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java index ee1edb8..084ea32 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java @@ -19,6 +19,7 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; +import de.oceanlabs.mcp.mcinjector.data.Classpath; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; @@ -50,7 +51,8 @@ static void process( Path ctrIn, Path excIn, Path excOut, Path prmIn, Path prmOut, - LVTNaming naming) + LVTNaming naming, + Path[] classpath) throws IOException { if (accIn != null) @@ -61,6 +63,8 @@ static void process( Parameters.INSTANCE.loadLegacy(ctrIn); if (excIn != null) Exceptions.INSTANCE.load(excIn); + Classpath.INSTANCE.load(classpath); + Classpath.INSTANCE.add(in); MCInjector.LOG.info("Processing: " + in); MCInjector.LOG.info(" Output: " + out); diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java b/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java index cfd40b5..f835a41 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java @@ -10,12 +10,16 @@ import java.util.Set; import java.util.logging.Level; +import de.oceanlabs.mcp.mcinjector.data.Classpath; import de.oceanlabs.mcp.mcinjector.data.Parameters; +import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LocalVariableNode; import org.objectweb.asm.tree.MethodNode; @@ -69,6 +73,7 @@ public void visitEnd() super.visitEnd(); processLVT(className, name, desc, MCInjectorImpl.getMethodNode(mv)); } + }; } @@ -107,6 +112,15 @@ private void processLVT(String cls, String name, String desc, MethodNode mn) } types.addAll(Arrays.asList(Type.getArgumentTypes(mn.desc))); + ClassNode currentClass = Classpath.INSTANCE.loadClass("L" + cls + ";"); + List potentialSupers = Classpath.INSTANCE.findOverrides(currentClass, mn); + + if (potentialSupers.size() > 1) { + if (mn.visibleAnnotations == null) { + mn.visibleAnnotations = new ArrayList<>(); + } + mn.visibleAnnotations.add(new AnnotationNode("java/lang/Override")); + } //Skip anything with no params if (types.size() == 0) @@ -117,7 +131,7 @@ private void processLVT(String cls, String name, String desc, MethodNode mn) if (name.matches("func_\\d+_.+")) // A srg name method params are just p_MethodID_ParamIndex_ nameFormat = "p_" + name.substring(5, name.indexOf('_', 5)) + "_%s_"; else if(!isSynthetic(mn)) - nameFormat = "p_" + Parameters.INSTANCE.getName(className, name, desc, types.size() > params.size(), isStatic) + "_%s_"; //assign new name only if there are names remaining + nameFormat = "p_" + Parameters.INSTANCE.getName(potentialSupers, cls, name, desc, types.size() > params.size(), isStatic) + "_%s_"; //assign new name only if there are names remaining else nameFormat = "p_%s_"; //don't really care about synthetics for (int x = params.size(), y = x; x < types.size(); x++) diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java new file mode 100644 index 0000000..56ab12a --- /dev/null +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java @@ -0,0 +1,106 @@ +package de.oceanlabs.mcp.mcinjector.data; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +public enum Classpath { + + INSTANCE; + + private Map classes; + private List classpath; + + public void load(Path[] classpath) throws IOException { + this.classes = new HashMap<>(); + this.classpath = new ArrayList<>(); + if (classpath != null) { + for (Path path : classpath) { + add(path); + } + } + } + + public void add(Path path) throws IOException { + File toFile = path.toFile(); + ZipFile zipFile = new ZipFile(toFile); + this.classpath.add(zipFile); + } + + private String descriptorToPath(String s){ + if (s.startsWith("L") && s.endsWith(";")) { + return s.substring(1, s.length()-1) + ".class"; + } + throw new IllegalArgumentException("Invalid class " + s); + } + + public ClassNode loadClass(String name) { + ClassNode node = classes.get(name); + String path = descriptorToPath(name); + if (node == null) { + try { + for (ZipFile zf : classpath) { +// System.out.println("Looking in " + zf.getName()); + ZipEntry ze = zf.getEntry(path); + if (ze != null) { + ClassReader cr = new ClassReader(zf.getInputStream(ze)); + node = new ClassNode(); + cr.accept(node, 0); + classes.put(name, node); + return node; + } + } + ClassReader cr = new ClassReader(Classpath.class.getClassLoader().getResourceAsStream(path)); + node = new ClassNode(); + cr.accept(node, 0); + classes.put(name, node); + } catch (IOException e) { + throw new RuntimeException("Class " + name + " = " + path, e); + } + } + return node; + } + + public List findOverrides(ClassNode node, MethodNode method) { + boolean found = false; + for (MethodNode mn : node.methods) { + if (mn.name.equals(method.name) && mn.desc.equals(method.desc)) { + found = true; + break; + } + } + if (!found) { + return new ArrayList<>(); + } + if ((method.access & Opcodes.ACC_PRIVATE) != 0) { + ArrayList arr = new ArrayList<>(); + arr.add(node); + return arr; + } + if (node.superName == null) { + ArrayList arr = new ArrayList<>(); + arr.add(node); + return arr; + } + List parent = findOverrides(loadClass("L" + node.superName + ";"), method); + for (String ifn : node.interfaces) { + parent.addAll(findOverrides(loadClass("L" + ifn + ";"), method)); + } + parent.add(node); + return parent; + } + +} diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java index 94cf7e5..0ef1b93 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java @@ -1,6 +1,7 @@ package de.oceanlabs.mcp.mcinjector.data; import de.oceanlabs.mcp.mcinjector.MCInjector; +import org.objectweb.asm.tree.ClassNode; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -10,6 +11,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.OptionalInt; import java.util.stream.Collectors; public enum Parameters @@ -24,6 +28,7 @@ public boolean load(Path file) { this.fromDesc.clear(); this.fromID.clear(); + System.out.println(file.toFile().exists()); try { MCInjector.LOG.fine("Loading Parameters from: " + file); @@ -101,10 +106,20 @@ public void setName(String cls, String method, String desc, int id) this.fromID .put(id, cls + " " + method + " " + desc); } - public String getName(String cls, String method, String desc, boolean generate, boolean isStatic) + public String getName(List supers, String cls, String method, String desc, boolean generate, boolean isStatic) { - Integer id = this.fromDesc.get(cls + " " + method + " " + desc); - if (id == null) + String canonicalSuper = supers.get(0).name; + Integer cid = this.fromDesc.get(canonicalSuper + " " + method + " " + desc); + if (cid != null) { + return cid.toString(); + } + OptionalInt id = supers.stream().skip(1) + .map(s -> s + " " + method + " " + desc) + .map(fromDesc::get) + .filter(Objects::nonNull) + .mapToInt(x -> x) + .min(); + if (!id.isPresent()) { if (!generate) return method; //if we are not generating new names we will return the old parameter format, _p_funcname_x_ @@ -112,6 +127,6 @@ public String getName(String cls, String method, String desc, boolean generate, this.setName(cls, method, desc, newId); return Integer.toString(newId); } - return Integer.toString(id); + return id.toString(); } } From 0cdacd8c82f89afbe5af382026a9751438b7ba20 Mon Sep 17 00:00:00 2001 From: JoJoDeveloping Date: Wed, 29 Jan 2020 14:07:05 +0100 Subject: [PATCH 3/3] Add way better inheritance walking, @Override injection, better config passing options, ... --- .../oceanlabs/mcp/mcinjector/MCInjector.java | 29 +- .../mcp/mcinjector/MCInjectorImpl.java | 5 +- .../mcp/mcinjector/adaptors/ApplyMap.java | 5 +- .../mcp/mcinjector/data/Classpath.java | 711 ++++++++++++++++-- .../mcp/mcinjector/data/Parameters.java | 52 +- 5 files changed, 713 insertions(+), 89 deletions(-) diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java index b1158b4..d4fc89f 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjector.java @@ -6,18 +6,22 @@ import java.io.PrintStream; import java.lang.reflect.Method; import java.nio.file.FileSystem; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Optional; +import java.util.Set; import java.util.logging.FileHandler; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.stream.Collectors; +import java.util.stream.Stream; import de.oceanlabs.mcp.mcinjector.lvt.LVTNaming; import joptsimple.OptionException; @@ -36,7 +40,7 @@ public class MCInjector private Path accIn, accOut; private Path ctrIn; private Path prmIn, prmOut; - private Path[] classpath; + private List classpath; private LVTNaming lvt; public MCInjector(Path fileIn, Path fileOut) @@ -80,7 +84,7 @@ public void publish(LogRecord record) return this; } - private MCInjector classpath(Path[] clsPath) { + private MCInjector classpath(List clsPath) { this.classpath = clsPath; return this; } @@ -204,6 +208,7 @@ public static void main(String[] args) throws Exception OptionSpec ver = parser.accepts("version") .forHelp(); OptionSpec in = parser.accepts("in") .withRequiredArg().withValuesConvertedBy(PATH_ARG).required(); OptionSpec out = parser.accepts("out") .withRequiredArg().withValuesConvertedBy(PATH_ARG); + OptionSpec cfg = parser.accepts("cfg") .withRequiredArg().withValuesConvertedBy(PATH_ARG); OptionSpec log = parser.accepts("log") .withRequiredArg().withValuesConvertedBy(PATH_ARG); OptionSpec exc = parser.accepts("exc") .withRequiredArg().withValuesConvertedBy(PATH_ARG); OptionSpec excOut = parser.accepts("excOut") .withRequiredArg().withValuesConvertedBy(PATH_ARG); @@ -219,7 +224,22 @@ public static void main(String[] args) throws Exception try { - OptionSet o = parser.parse(args); + OptionSet o; + Set extraArgsFiles = new HashSet<>(); + while (true) { + o = parser.parse(Stream.concat(Arrays.stream(args), extraArgsFiles.stream().flatMap(path -> { + try { + return Files.lines(path); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + })).toArray(String[]::new)); + if (o.valuesOf(cfg).size() == extraArgsFiles.size()) { + break; + } + extraArgsFiles.addAll(o.valuesOf(cfg)); + } if (o.has(help)) { System.out.println(VERSION); @@ -237,6 +257,7 @@ else if (o.has(ctrOut)) return; } + MCInjector.LOG.setUseParentHandlers(false); MCInjector.LOG.setLevel(o.valueOf(logLvl)); @@ -263,7 +284,7 @@ else if (o.has(ctrOut)) .log() .lvt(o.valueOf(lvt)) .log(o.valueOf(log)) - .classpath(o.valuesOf(clsPath).stream().flatMap(Arrays::stream).toArray(Path[]::new)) + .classpath(o.valuesOf(clsPath).stream().flatMap(Arrays::stream).collect(Collectors.toList())) .exceptions(o.valueOf(exc)) .exceptionsOut(o.valueOf(excOut)) .access(o.valueOf(acc)) diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java index 084ea32..4bb9f17 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/MCInjectorImpl.java @@ -52,7 +52,7 @@ static void process( Path excIn, Path excOut, Path prmIn, Path prmOut, LVTNaming naming, - Path[] classpath) + List classpath) throws IOException { if (accIn != null) @@ -63,8 +63,7 @@ static void process( Parameters.INSTANCE.loadLegacy(ctrIn); if (excIn != null) Exceptions.INSTANCE.load(excIn); - Classpath.INSTANCE.load(classpath); - Classpath.INSTANCE.add(in); + Classpath.INSTANCE.load(in, classpath); MCInjector.LOG.info("Processing: " + in); MCInjector.LOG.info(" Output: " + out); diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java b/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java index f835a41..dcfcdaa 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/adaptors/ApplyMap.java @@ -112,8 +112,7 @@ private void processLVT(String cls, String name, String desc, MethodNode mn) } types.addAll(Arrays.asList(Type.getArgumentTypes(mn.desc))); - ClassNode currentClass = Classpath.INSTANCE.loadClass("L" + cls + ";"); - List potentialSupers = Classpath.INSTANCE.findOverrides(currentClass, mn); + List potentialSupers = Classpath.INSTANCE.getTree().getInfo(className).getMethodInfo(name, desc).getOverrides(); if (potentialSupers.size() > 1) { if (mn.visibleAnnotations == null) { @@ -131,7 +130,7 @@ private void processLVT(String cls, String name, String desc, MethodNode mn) if (name.matches("func_\\d+_.+")) // A srg name method params are just p_MethodID_ParamIndex_ nameFormat = "p_" + name.substring(5, name.indexOf('_', 5)) + "_%s_"; else if(!isSynthetic(mn)) - nameFormat = "p_" + Parameters.INSTANCE.getName(potentialSupers, cls, name, desc, types.size() > params.size(), isStatic) + "_%s_"; //assign new name only if there are names remaining + nameFormat = "p_" + Parameters.INSTANCE.getName(potentialSupers, cls, name, desc, types.size() > params.size()) + "_%s_"; //assign new name only if there are names remaining else nameFormat = "p_%s_"; //don't really care about synthetics for (int x = params.size(), y = x; x < types.size(); x++) diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java index 56ab12a..b1dde83 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Classpath.java @@ -1,106 +1,683 @@ package de.oceanlabs.mcp.mcinjector.data; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodNode; - -import java.io.File; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Array; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; +import java.util.Queue; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.function.Consumer; +import java.util.logging.Level; import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; -public enum Classpath { +import de.oceanlabs.mcp.mcinjector.MCInjector; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InvokeDynamicInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; +import static org.objectweb.asm.Opcodes.*; + +import de.oceanlabs.mcp.mcinjector.data.Classpath.ClassInfo.MethodInfo; + +public enum Classpath { INSTANCE; - private Map classes; - private List classpath; + private static final Handle LAMBDA_METAFACTORY = new Handle(Opcodes.H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false); + private Tree tree; - public void load(Path[] classpath) throws IOException { - this.classes = new HashMap<>(); - this.classpath = new ArrayList<>(); - if (classpath != null) { - for (Path path : classpath) { - add(path); + public Tree getTree() { + return tree; + } + + public void load(Path input, Collection libraries) { + tree = new Tree(); + + Set classes = tree.load(input); + + for (Path lib : libraries) + tree.load(lib); + + for (String cls : classes) + resolve(cls); + } + + //Recursive, but should be fine as we don't have class super complex class trees + private void resolve(String cls) { + ClassInfo info = tree.getInfo(cls); + if (info == null || info.resolved) + return; + + if (info.getSuper() != null) + resolve(info.getSuper()); + + if (info.interfaces != null) + for (String intf : info.interfaces) + resolve(intf); + + //TODO: Synthetics/Accessors? + + if (info.methods != null) { + //Synthetic Bouncers! + for (MethodInfo mtd : info.methods.values()) { + if (mtd.bouncer != null) { + Method owner = walkBouncers(mtd, info.name); + if (owner != null && !owner.owner.equals(info.name)) + mtd.bouncer.setOwner(owner); + } } + + //Resolve the 'root' owner of each method. + for (MethodInfo mtd : info.methods.values()) { + mtd.setOverrides(findOverrides(mtd, info.name, new ArrayList<>())); + } + } + + if (!info.isAbstract()) { + resolveAbstract(info); + } + + info.resolved = true; + } + + private Method walkBouncers(MethodInfo mtd, String owner) { + ClassInfo info = tree.getInfo(owner); + + if (info == null) + return null; + + if (info.methods != null) { + MethodInfo mine = info.methods.get(mtd.name + mtd.desc); + if (mine != null && ((mine.getAccess() & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE)) == 0 || owner.equals(mtd.getOwner()))) { + if (mine.bouncer == null) { + List owners = findOverrides(mine, owner, new ArrayList<>()); + if (owners.isEmpty()) + return new Method(owner, mine.name, mine.desc); + else if (owners.size() == 1) + return owners.iterator().next(); + else //We can't find just one owner... something's fucky... + return owners.iterator().next(); //Just pick one... + } + + for (MethodInfo m2 : info.methods.values()) { + Method target = m2.bouncer == null ? null : m2.bouncer.target; + if (target != null && mine.name.equals(target.name) && mine.desc.equals(target.desc)) { + if (m2.bouncer.owner != null) + return m2.bouncer.owner; + + Method ret = walkBouncers(m2, owner); + if (ret != null && !ret.owner.equals(owner)) { + m2.bouncer.setOwner(ret); + return ret; + } else { + MCInjector.LOG.warning(" Unable to walk: " + m2.name + ' ' + m2.desc + " for " + owner + '/' + mine.name + ' ' + mine.desc); + } + } + } + } + } + + if (info.getSuper() != null) { + Method ret = walkBouncers(mtd, info.getSuper()); + if (ret != null) + return ret; } + + if (info.interfaces != null) { + for (String intf : info.interfaces) { + Method ret = walkBouncers(mtd, intf); + if (ret != null) + return ret; + } + } + + return null; } - public void add(Path path) throws IOException { - File toFile = path.toFile(); - ZipFile zipFile = new ZipFile(toFile); - this.classpath.add(zipFile); + private List findOverrides(MethodInfo mtd, String owner, List overrides) { + if (mtd.isStatic() || mtd.isPrivate() || mtd.name.startsWith("<")) + return overrides; + + ClassInfo info = tree.getInfo(owner); + + if (info == null) + return overrides; + + if (info.methods != null) { + for (MethodInfo m : info.methods.values()) { + Method target = m.bouncer == null ? null : m.bouncer.target; + if (target != null && mtd.name.equals(target.name) && mtd.desc.equals(target.desc)) { + //overrides.add(new Method(info.name, m.name, m.desc)); //Don't add overrides for self-methods + findOverrides(m, info.name, overrides); + } + } + + MethodInfo mine = info.methods.get(mtd.name + mtd.desc); + if (mine != null && mine != mtd && (mine.getAccess() & (Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE)) == 0) { + if (mine.getOverrides().isEmpty()) { + overrides.add(new Method(info.name, mine.name, mine.desc)); + } else { + overrides.addAll(mine.getOverrides()); + } + } + } + + if (info.getSuper() != null) + findOverrides(mtd, info.getSuper(), overrides); + + if (info.interfaces != null) { + for (String intf : info.interfaces) + findOverrides(mtd, intf, overrides); + } + + return overrides; } - private String descriptorToPath(String s){ - if (s.startsWith("L") && s.endsWith(";")) { - return s.substring(1, s.length()-1) + ".class"; + private void resolveAbstract( ClassInfo cls) { + Map abs = new HashMap<>(); + Set known = new TreeSet<>(); + Queue que = new LinkedList<>(); + Consumer add = c -> { + if (!known.contains(c)) { + que.add(c); + known.add(c); + } + }; + + add.accept(cls.name); + + while (!que.isEmpty()) { + ClassInfo info = tree.getInfo(que.poll()); + if (info == null) + continue; + + if (info.methods != null) + info.methods.values().stream() + .filter(MethodInfo::isAbstract) + .filter(mtd -> mtd.overrides == null) //We only want the roots + .forEach(mtd -> abs.put(mtd.name + mtd.desc, info.name)); + + if (info.getSuper() != null) + add.accept(info.getSuper()); + + if (info.interfaces != null) + info.interfaces.forEach(add); + } + + known.clear(); + add.accept(cls.name); + + while (!que.isEmpty()) { + ClassInfo info = tree.getInfo(que.poll()); + if (info == null) + continue; + + if (info.methods != null) { + for (MethodInfo mtd : info.methods.values()) { + if (mtd.isAbstract()) + continue; + + String towner = abs.remove(mtd.name + mtd.desc); + if (towner == null) + continue; + Method target = new Method(towner, mtd.name, mtd.desc); + + if (mtd.overrides != null) { + for (Method omh : mtd.overrides) { + ClassInfo ocls = tree.getInfo(omh.owner); + if (towner.equals(omh.owner) || ocls == null) //Error? + continue; + MethodInfo omtd = ocls.methods == null ? null : ocls.methods.get(omh.name + omh.desc); + if (omtd == null) //Error? + continue; + if (omtd.overrides != null) { + if (!omtd.overrides.contains(target)) + omtd.overrides.add(target); + } else { + omtd.setOverrides(Collections.singletonList(target)); + } + break; + } + } else { + if (mtd.overrides != null) { + if (!mtd.overrides.contains(target)) + mtd.overrides.add(target); + } else { + mtd.setOverrides(Collections.singletonList(target)); + } + } + } + } + + if (info.getSuper() != null) + add.accept(info.getSuper()); + + if (info.interfaces != null) + info.interfaces.forEach(add); + } + + if (!abs.isEmpty()) { + MCInjector.LOG.log(Level.SEVERE, " Unresolved abstracts for: " + cls.name); + abs.forEach((mtd,c) -> MCInjector.LOG.log(Level.SEVERE, " " + c + "/" + mtd)); } - throw new IllegalArgumentException("Invalid class " + s); } - public ClassNode loadClass(String name) { - ClassNode node = classes.get(name); - String path = descriptorToPath(name); - if (node == null) { - try { - for (ZipFile zf : classpath) { -// System.out.println("Looking in " + zf.getName()); - ZipEntry ze = zf.getEntry(path); - if (ze != null) { - ClassReader cr = new ClassReader(zf.getInputStream(ze)); - node = new ClassNode(); - cr.accept(node, 0); - classes.put(name, node); - return node; + public static class Tree { + private Map classes = new HashMap<>(); + private Set negative = new HashSet<>(); + private Map sources = new HashMap<>(); + + public Set load(Path path) { + try (ZipInputStream jin = new ZipInputStream(Files.newInputStream(path))) { + Set classes = new TreeSet<>(); + + ZipEntry entry = null; + while ((entry = jin.getNextEntry()) != null) { + String name = entry.getName(); + if (entry.isDirectory() || !name.endsWith(".class")) + continue; + + String cls = name.substring(0, name.length() - 6); + if (!sources.containsKey(cls)) { + byte[] data = readStreamFully(jin); + sources.put(cls, data); + classes.add(cls); } } - ClassReader cr = new ClassReader(Classpath.class.getClassLoader().getResourceAsStream(path)); - node = new ClassNode(); - cr.accept(node, 0); - classes.put(name, node); + + return classes; } catch (IOException e) { - throw new RuntimeException("Class " + name + " = " + path, e); + MCInjector.LOG.log(Level.SEVERE, "Failed to load: " + path.toString(), e); + } + return Collections.emptySet(); + } + + public ClassInfo getInfo(String cls) { + if (negative.contains(cls)) + return null; + + ClassInfo ret = classes.get(cls); + if (ret == null) { + byte[] data = sources.remove(cls); + if (data == null) { + try (InputStream in = Classpath.class.getClassLoader().getResourceAsStream(cls + ".class")) { + if (in == null) { + MCInjector.LOG.info(" Failed to find class: " + cls); + negative.add(cls); + return null; + } + data = readStreamFully(in); + } catch (Throwable e) { + MCInjector.LOG.info(" Failed to find class: " + cls); + negative.add(cls); + return null; + } + } + ClassNode classNode = new ClassNode(); + ClassReader classReader = new ClassReader(data); + classReader.accept(classNode, 0); + ret = new ClassInfo(classNode); + classes.put(cls, ret); } + return ret; } - return node; } - public List findOverrides(ClassNode node, MethodNode method) { - boolean found = false; - for (MethodNode mn : node.methods) { - if (mn.name.equals(method.name) && mn.desc.equals(method.desc)) { - found = true; - break; + public interface IAccessible { + int getAccess(); + + default boolean isInterface() { + return ((getAccess() & Opcodes.ACC_INTERFACE) != 0); + } + + default boolean isAbstract() { + return ((getAccess() & Opcodes.ACC_ABSTRACT) != 0); + } + + default boolean isSynthetic() { + return ((getAccess() & Opcodes.ACC_SYNTHETIC) != 0); + } + + default boolean isAnnotation() { + return ((getAccess() & Opcodes.ACC_ANNOTATION) != 0); + } + + default boolean isEnum() { + return ((getAccess() & Opcodes.ACC_ENUM) != 0); + } + + default boolean isPackagePrivate() { + return (getAccess() & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) == 0; + } + + default boolean isPublic() { + return (getAccess() & Opcodes.ACC_PUBLIC) != 0; + } + + default boolean isPrivate() { + return (getAccess() & Opcodes.ACC_PRIVATE) != 0; + } + + default boolean isProtected() { + return (getAccess() & Opcodes.ACC_PROTECTED) != 0; + } + + default boolean isStatic() { + return (getAccess() & Opcodes.ACC_STATIC) != 0; + } + + default boolean isFinal() { + return (getAccess() & Opcodes.ACC_FINAL) != 0; + } + } + + @SuppressWarnings("unused") + public static class ClassInfo implements IAccessible { + private final transient String name; + private final String superName; + private final List interfaces; + private final Integer access; + private final String signature; + private final Map fields; + private final Map methods; + private transient boolean resolved = false; + + private ClassInfo(ClassNode node) { + this.name = node.name; + this.superName = "java/lang/Object".equals(node.superName) ? null : node.superName; + this.interfaces = node.interfaces != null && !node.interfaces.isEmpty() ? new ArrayList<>(node.interfaces) : null; + this.access = node.access == 0 ? null : node.access; + this.signature = node.signature; + + if (node.fields == null || node.fields.isEmpty()) { + this.fields = null; + } else { + this.fields = new TreeMap<>(); + node.fields.stream().forEach(fld -> this.fields.put(fld.name, new FieldInfo(fld))); + } + + if (node.methods == null || node.methods.isEmpty()) { + this.methods = null; + } else { + //Gather Lambda methods so we can skip them in bouncers? + Set lambdas = new HashSet<>(); + for (MethodNode m : node.methods) { + for (AbstractInsnNode asn : (Iterable)() -> m.instructions.iterator()) { + if (asn instanceof InvokeDynamicInsnNode) { + InvokeDynamicInsnNode idn = (InvokeDynamicInsnNode)asn; + if (LAMBDA_METAFACTORY.equals(idn.bsm) && idn.bsmArgs != null && idn.bsmArgs.length == 3 && idn.bsmArgs[1] instanceof Handle) { + Handle target = ((Handle)idn.bsmArgs[1]); + lambdas.add(target.getOwner() + '/' + target.getName() + target.getDesc()); + } + } + } + } + + this.methods = new TreeMap<>(); + node.methods.forEach(mtd -> { + String key = mtd.name + mtd.desc; + this.methods.put(key, new MethodInfo(mtd, lambdas.contains(this.name + '/' + key))); + }); } } - if (!found) { - return new ArrayList<>(); + + public String getSuper() { + return this.superName == null && !"java/lang/Object".equals(this.name) ? "java/lang/Object" : this.superName; } - if ((method.access & Opcodes.ACC_PRIVATE) != 0) { - ArrayList arr = new ArrayList<>(); - arr.add(node); - return arr; + + @Override + public int getAccess() { + return access == null ? 0 : access; } - if (node.superName == null) { - ArrayList arr = new ArrayList<>(); - arr.add(node); - return arr; + + @Override + public String toString() { + return Classpath.getAccess(getAccess()) + ' ' + this.name; } - List parent = findOverrides(loadClass("L" + node.superName + ";"), method); - for (String ifn : node.interfaces) { - parent.addAll(findOverrides(loadClass("L" + ifn + ";"), method)); + + public MethodInfo getMethodInfo(String name, String desc) { + return methods.get(name + desc); + } + + public class FieldInfo implements IAccessible { + private final transient String name; + private final transient String desc; + private final Integer access; + private final String signature; + private String force; + + private FieldInfo(FieldNode node) { + this.name = node.name; + this.desc = node.desc; + this.access = node.access == 0 ? null : node.access; + this.signature = node.signature; + } + + public void forceName(String name) { + this.force = name; + } + + @Override + public int getAccess() { + return access == null ? 0 : access; + } + + @Override + public String toString() { + return Classpath.getAccess(getAccess()) + ' ' + this.desc + ' ' + this.name; + } + } + + public class MethodInfo implements IAccessible { + private final transient String name; + private final transient String desc; + private final Integer access; + private final String signature; + private final Bounce bouncer; + private String force; + private List overrides; + + private MethodInfo(MethodNode node, boolean lambda) { + this.name = node.name; + this.desc = node.desc; + this.access = node.access == 0 ? null : node.access; + this.signature = node.signature; + + Bounce bounce = null; + if (!lambda && (node.access & (Opcodes.ACC_SYNTHETIC | Opcodes.ACC_BRIDGE)) != 0 && (node.access & Opcodes.ACC_STATIC) == 0) { + AbstractInsnNode start = node.instructions.getFirst(); + if (start instanceof LabelNode && start.getNext() instanceof LineNumberNode) + start = start.getNext().getNext(); + + if (start instanceof VarInsnNode) { + VarInsnNode n = (VarInsnNode)start; + if (n.var == 0 && n.getOpcode() == Opcodes.ALOAD) { + AbstractInsnNode end = node.instructions.getLast(); + if (end instanceof LabelNode) + end = end.getPrevious(); + + if (end.getOpcode() >= Opcodes.IRETURN && end.getOpcode() <= Opcodes.RETURN) + end = end.getPrevious(); + + if (end instanceof MethodInsnNode) { + Type[] args = Type.getArgumentTypes(node.desc); + int var = 1; + int index = 0; + start = start.getNext(); + while (start != end) { + if (start instanceof VarInsnNode) { + if (((VarInsnNode)start).var != var || index + 1 > args.length) { + //Arguments are switched around, so seems like lambda! + end = null; + break; + } + var += args[index++].getSize(); + } else if (start.getOpcode() == Opcodes.INSTANCEOF || start.getOpcode() == Opcodes.CHECKCAST) { + //Valid! + } else { + // Anything else is invalid in a bouncer {As far as I know}, so we're most likely a lambda + end = null; + break; + } + start = start.getNext(); + } + + MethodInsnNode mtd = (MethodInsnNode)end; + if (end != null && mtd.owner.equals(ClassInfo.this.name) && Type.getArgumentsAndReturnSizes(node.desc) == Type.getArgumentsAndReturnSizes(mtd.desc)) + bounce = new Bounce(new Method(mtd.owner, mtd.name, mtd.desc)); + } + } + } + } + this.bouncer = bounce; + } + + @Override + public int getAccess() { + return access == null ? 0 : access; + } + + public void forceName(String value) { + this.force = value; + } + + public void setOverrides(List value) { + this.overrides = value.isEmpty() ? null : value; + } + + public List getOverrides() { + return this.overrides == null ? Collections.emptyList() : this.overrides; + } + + public String getOwner() { + return ClassInfo.this.name; + } + + @Override + public String toString() { + return Classpath.getAccess(getAccess()) + ' ' + this.name + ' ' + this.desc; + } + + public String getName() { + return name; + } + + public String getDesc() { + return desc; + } + } + } + + public static class Method implements Comparable { + private final String owner; + private final String name; + private final String desc; + + private Method(String owner, String name, String desc) { + this.owner = owner; + this.name = name; + this.desc = desc; + } + + @Override + public String toString() { + return this.owner + '/' + this.name + this.desc; + } + + @Override + public int hashCode() { + return this.toString().hashCode(); + } + + @Override + public boolean equals(Object o) { + return o instanceof Method && o.toString().equals(toString()); + } + + private int compare(int a, int b) { + return a != 0 ? a : b; + } + + @Override + public int compareTo(Method o) { + return compare(owner.compareTo(o.owner), compare(name.compareTo(o.name), desc.compareTo(o.desc))); + } + + public String getDesc() { + return desc; + } + + public String getName() { + return name; + } + + public String getOwner() { + return owner; + } + } + + private static class Bounce { + private final Method target; + private Method owner; + + private Bounce(Method target) { + this.target = target; + } + + public void setOwner(Method value) { + this.owner = value; + } + + @Override + public String toString() { + return this.target + " -> " + this.owner; } - parent.add(node); - return parent; } -} + + public static byte[] readStreamFully(InputStream is) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(Math.max(8192, is.available())); + byte[] buffer = new byte[8192]; + int read; + while((read = is.read(buffer)) >= 0) { + baos.write(buffer, 0, read); + } + return baos.toByteArray(); + } + + private static int[] FLAGS = new int[] {ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_SUPER, ACC_SYNCHRONIZED, ACC_VOLATILE, ACC_BRIDGE, ACC_VARARGS, ACC_TRANSIENT, ACC_NATIVE, ACC_INTERFACE, ACC_ABSTRACT, ACC_STRICT, ACC_SYNTHETIC, ACC_ANNOTATION, ACC_ENUM}; + private static String[] NAMES = new String[]{ "public", "private", "protected", "static", "final", "super", "synchronized", "volitize", "bridge", "varargs", "transient", "native", "interface", "abstract", "strict", "synthetic", "annotation", "enum"}; + public static String getAccess(int access) { + StringBuilder out = new StringBuilder(); + for (int x = 0; x < FLAGS.length; x++) { + if ((access & FLAGS[x]) == FLAGS[x]) + out.append(NAMES[x]).append(" "); + } + if (out.length() > 0) + return out.toString().trim(); + return "default"; + } +} \ No newline at end of file diff --git a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java index 0ef1b93..84cccfc 100644 --- a/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java +++ b/src/main/java/de/oceanlabs/mcp/mcinjector/data/Parameters.java @@ -106,26 +106,54 @@ public void setName(String cls, String method, String desc, int id) this.fromID .put(id, cls + " " + method + " " + desc); } - public String getName(List supers, String cls, String method, String desc, boolean generate, boolean isStatic) + public String getName(List overrides, String cls, String method, String desc, boolean generate) { - String canonicalSuper = supers.get(0).name; - Integer cid = this.fromDesc.get(canonicalSuper + " " + method + " " + desc); - if (cid != null) { - return cid.toString(); + OptionalInt id; + if (!method.startsWith("<") && !overrides.isEmpty()) { + Classpath.Method canonicalSuper = overrides.get(0); + Integer cid = this.fromDesc.get(canonicalSuper.getOwner() + " " + canonicalSuper.getName() + " " + canonicalSuper.getDesc()); + if (cid != null) { + System.out.println("Using param name from superclass canon " + canonicalSuper + " for " + cls + " " + method + " " + desc); + return cid.toString(); + } + id = overrides.stream().skip(1) + .map(s -> s.getOwner() + " " + s.getName() + " " + s.getDesc()) + .map(fromDesc::get) + .filter(Objects::nonNull) + .mapToInt(x -> x) + .min(); + + if (!id.isPresent()) { + Integer i = fromDesc.get(cls + " " + method + " " + desc); + id = i == null ? OptionalInt.empty() : OptionalInt.of(i); + } + } else { + Integer i = fromDesc.get(cls + " " + method + " " + desc); + id = i == null ? OptionalInt.empty() : OptionalInt.of(i); } - OptionalInt id = supers.stream().skip(1) - .map(s -> s + " " + method + " " + desc) - .map(fromDesc::get) - .filter(Objects::nonNull) - .mapToInt(x -> x) - .min(); if (!id.isPresent()) { - if (!generate) + if (!generate) { + if (!method.equals("") && !desc.equals("()V")) { + System.out.println("Could not find param id for " + cls + " " + method + " " + desc); + for (Classpath.Method md : overrides) { + System.out.println("Tried: " + md.toString()); + } + } return method; //if we are not generating new names we will return the old parameter format, _p_funcname_x_ + } int newId = ++maxID; this.setName(cls, method, desc, newId); return Integer.toString(newId); + } else { + for(Classpath.Method mi : overrides) { + if (fromDesc.containsKey(mi.getOwner() + " " + mi.getName() + " " + mi.getDesc())) { + if (fromDesc.get(mi.getOwner() + " " + mi.getName() + " " + mi.getDesc()) == id.getAsInt()) { + System.out.println("Using param name from superclass noc" + mi.getOwner() + " " + mi.getName()+mi.getDesc() + " for " + cls + " " + method + " " + desc); + break; + } + } + } } return id.toString(); }