From 52f82017e7aa802ba2aed97977cc742a48c33d57 Mon Sep 17 00:00:00 2001 From: Chicken-Bomes Date: Mon, 28 Apr 2014 19:40:13 +1000 Subject: [PATCH] Upgraded ASM lib --- src/codechicken/lib/asm/ASMBlock.java | 173 +++++++++++ src/codechicken/lib/asm/ASMHelper.java | 248 +++++---------- src/codechicken/lib/asm/ASMReader.java | 159 ++++------ .../lib/asm/ImportantInsnVisitor.java | 36 +++ src/codechicken/lib/asm/InsnComparator.java | 189 ++++++++++++ src/codechicken/lib/asm/InsnListPrinter.java | 197 ------------ src/codechicken/lib/asm/InsnListSection.java | 241 +++++++++++++++ .../lib/asm/InstructionComparator.java | 239 --------------- .../lib/asm/LocalVariablesSorterVisitor.java | 37 +++ .../lib/asm/ModularASMTransformer.java | 285 ++++++++++++++++++ src/codechicken/lib/asm/ObfMapping.java | 28 +- src/codechicken/lib/config/ConfigFile.java | 37 +-- .../lib/config/ConfigTagParent.java | 2 +- .../lib/config/DefaultingConfigFile.java | 18 ++ 14 files changed, 1142 insertions(+), 747 deletions(-) create mode 100644 src/codechicken/lib/asm/ASMBlock.java create mode 100644 src/codechicken/lib/asm/ImportantInsnVisitor.java create mode 100644 src/codechicken/lib/asm/InsnComparator.java delete mode 100644 src/codechicken/lib/asm/InsnListPrinter.java create mode 100644 src/codechicken/lib/asm/InsnListSection.java delete mode 100644 src/codechicken/lib/asm/InstructionComparator.java create mode 100644 src/codechicken/lib/asm/LocalVariablesSorterVisitor.java create mode 100644 src/codechicken/lib/asm/ModularASMTransformer.java create mode 100644 src/codechicken/lib/config/DefaultingConfigFile.java diff --git a/src/codechicken/lib/asm/ASMBlock.java b/src/codechicken/lib/asm/ASMBlock.java new file mode 100644 index 0000000..c355437 --- /dev/null +++ b/src/codechicken/lib/asm/ASMBlock.java @@ -0,0 +1,173 @@ +package codechicken.lib.asm; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableMap; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; + +import java.util.*; +import java.util.Map.Entry; + +import static org.objectweb.asm.tree.AbstractInsnNode.*; + +public class ASMBlock +{ + public InsnListSection list; + private BiMap labels; + + public ASMBlock(InsnListSection list, BiMap labels) { + this.list = list; + this.labels = labels; + } + + public ASMBlock(InsnListSection list) { + this(list, HashBiMap.create()); + } + + public ASMBlock(InsnList list) { + this(new InsnListSection(list)); + } + + public ASMBlock() { + this(new InsnListSection()); + } + + public LabelNode getOrAdd(String s) { + LabelNode l = get(s); + if (l == null) + labels.put(s, l = new LabelNode()); + return l; + } + + public LabelNode get(String s) { + return labels.get(s); + } + + public void replaceLabels(Map labelMap, Set usedLabels) { + for (AbstractInsnNode insn : list) + switch (insn.getType()) { + case LABEL: + AbstractInsnNode insn2 = insn.clone(labelMap); + if (insn2 == insn)//identity mapping + continue; + if (usedLabels.contains(insn2)) + throw new IllegalStateException("LabelNode cannot be a part of two InsnLists"); + list.replace(insn, insn2); + break; + case JUMP_INSN: + case FRAME: + case LOOKUPSWITCH_INSN: + case TABLESWITCH_INSN: + list.replace(insn, insn.clone(labelMap)); + } + + for(Entry entry : labelMap.entrySet()) { + String key = labels.inverse().get(entry.getKey()); + if(key != null) + labels.put(key, entry.getValue()); + } + } + + public void replaceLabels(Map labelMap) { + replaceLabels(labelMap, Collections.EMPTY_SET); + } + + public void replaceLabel(String s, LabelNode l) { + LabelNode old = get(s); + if (old != null) + replaceLabels(ImmutableMap.of(old, l)); + } + + /** + * Pulls all common labels from other into this + * @return this + */ + public ASMBlock mergeLabels(ASMBlock other) { + if(labels.isEmpty() || other.labels.isEmpty()) + return this; + + //common labels, give them our nodes + HashMap labelMap = list.identityLabelMap(); + for(Entry entry : other.labels.entrySet()) { + LabelNode old = labels.get(entry.getKey()); + if(old != null) + labelMap.put(old, entry.getValue()); + } + HashSet usedLabels = new HashSet(); + for (AbstractInsnNode insn = other.list.list.getFirst(); insn != null; insn = insn.getNext()) + if(insn.getType() == LABEL) + usedLabels.add((LabelNode) insn); + + replaceLabels(labelMap, usedLabels); + return this; + } + + /** + * Like mergeLabels but pulls insns from other list into this so LabelNodes can be transferred + * @return this + */ + public ASMBlock pullLabels(ASMBlock other) { + other.list.remove(); + return mergeLabels(other); + } + + public ASMBlock copy() { + BiMap labels = HashBiMap.create(); + Map labelMap = list.cloneLabels(); + + for(Entry entry : this.labels.entrySet()) + labels.put(entry.getKey(), labelMap.get(entry.getValue())); + + return new ASMBlock(list.copy(labelMap), labels); + } + + public ASMBlock applyLabels(InsnListSection list2) { + if(labels.isEmpty()) + return new ASMBlock(list2); + + Set cFlowLabels1 = labels.values(); + Set cFlowLabels2 = InsnComparator.getControlFlowLabels(list2); + ASMBlock block = new ASMBlock(list2); + + HashMap labelMap = new HashMap(); + + for(int i = 0, k = 0; i < list.size() && k < list2.size(); ) { + AbstractInsnNode insn1 = list.get(i); + if(!InsnComparator.insnImportant(insn1, cFlowLabels1)) { + i++; + continue; + } + + AbstractInsnNode insn2 = list2.get(k); + if(!InsnComparator.insnImportant(insn2, cFlowLabels2)) { + k++; + continue; + } + + if(insn1.getOpcode() != insn2.getOpcode()) + throw new IllegalArgumentException("Lists do not match:\n"+list+"\n\n"+list2); + + switch(insn1.getType()) { + case LABEL: + labelMap.put((LabelNode) insn1, (LabelNode) insn2); + break; + case JUMP_INSN: + labelMap.put(((JumpInsnNode) insn1).label, ((JumpInsnNode) insn2).label); + break; + } + i++; k++; + } + + for(Entry entry : labels.entrySet()) + block.labels.put(entry.getKey(), labelMap.get(entry.getValue())); + + return block; + } + + public InsnList rawListCopy() { + return list.copy().list; + } +} \ No newline at end of file diff --git a/src/codechicken/lib/asm/ASMHelper.java b/src/codechicken/lib/asm/ASMHelper.java index d4b03c1..7fa1aa3 100644 --- a/src/codechicken/lib/asm/ASMHelper.java +++ b/src/codechicken/lib/asm/ASMHelper.java @@ -1,76 +1,40 @@ package codechicken.lib.asm; -import codechicken.lib.asm.InstructionComparator.InsnListSection; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; +import codechicken.lib.config.ConfigFile; +import codechicken.lib.config.DefaultingConfigFile; +import cpw.mods.fml.relauncher.FMLInjectionData; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; import org.objectweb.asm.tree.*; +import org.objectweb.asm.util.TraceClassVisitor; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; public class ASMHelper { - public static class CodeBlock - { - public Label start = new Label(); - public Label end = new Label(); + public static ConfigFile config = loadConfig(); + public static Logger logger = LogManager.getLogger("CCL ASM"); + + private static ConfigFile loadConfig() { + File file = new File((File) FMLInjectionData.data()[6], "config/CodeChickenLib.cfg"); + if(ObfMapping.obfuscated) + return new DefaultingConfigFile(file); + else + return new ConfigFile(file).setComment("CodeChickenLib development configuration file."); } - public static class ForBlock extends CodeBlock + public static interface Acceptor { - public Label cmp = new Label(); - public Label inc = new Label(); - public Label body = new Label(); - } - - public static abstract class MethodAltercator - { - public final ObfMapping method; - - public MethodAltercator(ObfMapping method) { - this.method = method.toClassloading(); - } - - public abstract void alter(MethodNode mv); - } - - public static abstract class MethodWriter - { - public final int access; - public final ObfMapping method; - public final String[] exceptions; - - public MethodWriter(int access, ObfMapping method) { - this(access, method, null); - } - - public MethodWriter(int access, ObfMapping method, String[] exceptions) { - this.access = access; - this.method = method; - this.exceptions = exceptions; - } - - public abstract void write(MethodNode mv); - } - - public static class MethodInjector - { - public final ObfMapping method; - public final InsnList needle; - public final InsnList injection; - public final boolean before; - - public MethodInjector(ObfMapping method, InsnList needle, InsnList injection, boolean before) { - this.method = method; - this.needle = needle; - this.injection = injection; - this.before = before; - } + public void accept(ClassVisitor cv) throws IOException; } public static MethodNode findMethod(ObfMapping methodmap, ClassNode cnode) { @@ -104,91 +68,16 @@ public class ASMHelper return cw.toByteArray(); } - public static byte[] writeMethods(String name, byte[] bytes, Multimap writers) { - if (writers.containsKey(name)) { - ClassNode cnode = createClassNode(bytes); - - for (MethodWriter mw : writers.get(name)) { - MethodNode mv = findMethod(mw.method, cnode); - if (mv == null) - mv = (MethodNode) cnode.visitMethod(mw.access, mw.method.s_name, mw.method.s_desc, null, mw.exceptions); - - mv.access = mw.access; - mv.instructions.clear(); - mw.write(mv); - } - - bytes = createBytes(cnode, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - } - return bytes; + public static Map cloneLabels(InsnList list) { + return new InsnListSection(list).cloneLabels(); } - public static byte[] injectMethods(String name, byte[] bytes, Multimap injectors) { - if (injectors.containsKey(name)) { - ClassNode cnode = createClassNode(bytes); - - for (MethodInjector injector : injectors.get(name)) { - MethodNode method = findMethod(injector.method, cnode); - if (method == null) - throw new RuntimeException("Method not found: " + injector.method); - System.out.println("Injecting into " + injector.method + "\n" + printInsnList(injector.injection)); - - List callNodes; - if (injector.before) - callNodes = InstructionComparator.insnListFindStart(method.instructions, injector.needle); - else - callNodes = InstructionComparator.insnListFindEnd(method.instructions, injector.needle); - - if (callNodes.size() == 0) { - throw new RuntimeException("Needle not found in Haystack: " + injector.method + "\n" + printInsnList(injector.needle)); - } - - for (AbstractInsnNode node : callNodes) { - if (injector.before) { - System.out.println("Injected before: " + printInsn(node)); - method.instructions.insertBefore(node, cloneInsnList(injector.injection)); - } else { - System.out.println("Injected after: " + printInsn(node)); - method.instructions.insert(node, cloneInsnList(injector.injection)); - } - } - } - - bytes = createBytes(cnode, ClassWriter.COMPUTE_FRAMES); - } - return bytes; + public static InsnList cloneInsnList(InsnList list) { + return new InsnListSection(list).copy().list; } - public static String printInsnList(InsnList list) { - InsnListPrinter p = new InsnListPrinter(); - p.visitInsnList(list); - return p.textString(); - } - - public static String printInsn(AbstractInsnNode insn) { - InsnListPrinter p = new InsnListPrinter(); - p.visitInsn(insn); - return p.textString(); - } - - public static Map cloneLabels(InsnList insns) { - HashMap labelMap = new HashMap(); - for (AbstractInsnNode insn = insns.getFirst(); insn != null; insn = insn.getNext()) - if (insn.getType() == 8) - labelMap.put((LabelNode) insn, new LabelNode()); - return labelMap; - } - - public static InsnList cloneInsnList(InsnList insns) { - return cloneInsnList(cloneLabels(insns), insns); - } - - public static InsnList cloneInsnList(Map labelMap, InsnList insns) { - InsnList clone = new InsnList(); - for (AbstractInsnNode insn = insns.getFirst(); insn != null; insn = insn.getNext()) - clone.add(insn.clone(labelMap)); - - return clone; + public static InsnList cloneInsnList(Map labelMap, InsnList list) { + return new InsnListSection(list).copy(labelMap).list; } public static List cloneTryCatchBlocks(Map labelMap, List tcblocks) { @@ -204,7 +93,7 @@ public class ASMHelper } public static List cloneLocals(Map labelMap, List locals) { - ArrayList clone = new ArrayList(); + ArrayList clone = new ArrayList(locals.size()); for (LocalVariableNode node : locals) clone.add(new LocalVariableNode( node.name, node.desc, node.signature, @@ -226,28 +115,8 @@ public class ASMHelper dst.visitMaxs(src.maxStack, src.maxLocals); } - public static byte[] alterMethods(String name, byte[] bytes, HashMultimap altercators) { - if (altercators.containsKey(name)) { - ClassNode cnode = createClassNode(bytes); - - for (MethodAltercator injector : altercators.get(name)) { - MethodNode method = findMethod(injector.method, cnode); - if (method == null) - throw new RuntimeException("Method not found: " + injector.method); - - injector.alter(method); - } - - bytes = createBytes(cnode, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); - } - return bytes; - } - - - public static String printInsnList(InsnListSection subsection) { - InsnListPrinter p = new InsnListPrinter(); - p.visitInsnList(subsection); - return p.textString(); + public static String toString(InsnList list) { + return new InsnListSection(list).toString(); } public static int getLocal(List list, String name) { @@ -263,7 +132,7 @@ public class ASMHelper return found; } - public static void replaceMethodCode(MethodNode original, MethodNode replacement) { + public static void replaceMethod(MethodNode original, MethodNode replacement) { original.instructions.clear(); if (original.localVariables != null) original.localVariables.clear(); @@ -272,14 +141,51 @@ public class ASMHelper replacement.accept(original); } - public static void removeBlock(InsnList insns, InsnListSection block) { - AbstractInsnNode insn = block.first; - while (true) { - AbstractInsnNode next = insn.getNext(); - insns.remove(insn); - if (insn == block.last) - break; - insn = next; + public static void dump(Acceptor acceptor, File file, boolean filterImportant, boolean sortLocals) { + try { + if(!file.getParentFile().exists()) + file.getParentFile().mkdirs(); + if(!file.exists()) + file.createNewFile(); + + PrintWriter pout = new PrintWriter(file); + ClassVisitor cv = new TraceClassVisitor(pout); + if(filterImportant) cv = new ImportantInsnVisitor(cv); + if(sortLocals) cv = new LocalVariablesSorterVisitor(cv); + acceptor.accept(cv); + pout.close(); + } catch (IOException e) { + throw new RuntimeException(e); } } + + public static void dump(final byte[] bytes, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() + { + @Override + public void accept(ClassVisitor cv) { + new ClassReader(bytes).accept(cv, ClassReader.EXPAND_FRAMES); + } + }, file, filterImportant, sortLocals); + } + + public static void dump(final InputStream is, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() + { + @Override + public void accept(ClassVisitor cv) throws IOException { + new ClassReader(is).accept(cv, ClassReader.EXPAND_FRAMES); + } + }, file, filterImportant, sortLocals); + } + + public static void dump(final ClassNode cnode, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() + { + @Override + public void accept(ClassVisitor cv) { + cnode.accept(cv); + } + }, file, filterImportant, sortLocals); + } } diff --git a/src/codechicken/lib/asm/ASMReader.java b/src/codechicken/lib/asm/ASMReader.java index ce1aa5b..e5ca2d2 100644 --- a/src/codechicken/lib/asm/ASMReader.java +++ b/src/codechicken/lib/asm/ASMReader.java @@ -1,67 +1,22 @@ package codechicken.lib.asm; -import com.google.common.collect.ImmutableMap; import org.objectweb.asm.tree.*; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import static org.objectweb.asm.Opcodes.*; import static org.objectweb.asm.tree.AbstractInsnNode.*; public class ASMReader { - public static class ASMBlock - { - public InsnList insns = new InsnList(); - private HashMap labels = new HashMap(); - - public LabelNode getOrAdd(String s) - { - LabelNode l = get(s); - if(l == null) - labels.put(s, l = new LabelNode()); - return l; - } - - public LabelNode get(String s) - { - return labels.get(s); - } - - public void replace(String s, LabelNode l) - { - LabelNode old = get(s); - if(old != null) - { - Map map = ImmutableMap.of(old, l); - for(AbstractInsnNode insn = insns.getFirst(); insn != null; insn = insn.getNext()) - { - switch(insn.getType()) - { - case JUMP_INSN: - case FRAME: - case LOOKUPSWITCH_INSN: - case TABLESWITCH_INSN: - case LABEL: - insns.insert(insn, insn.clone(map)); - insns.remove(insn); - } - } - } - labels.put(s, l); - } - } - public static Map opCodes = new HashMap(); public static byte[] TYPE; - - static - { + + static { opCodes.put("NOP", NOP); opCodes.put("ACONST_NULL", ACONST_NULL); opCodes.put("ICONST_M1", ICONST_M1); @@ -219,7 +174,7 @@ public class ASMReader opCodes.put("MULTIANEWARRAY", MULTIANEWARRAY); opCodes.put("IFNULL", IFNULL); opCodes.put("IFNONNULL", IFNONNULL); - + //derived from classWriter, mapped to AbstractInsnNode TYPE = new byte[200]; String s = "AAAAAAAAAAAAAAAABBJ__CCCCC____________________AAAAAAAACC" @@ -230,95 +185,89 @@ public class ASMReader TYPE[i] = (byte) (s.charAt(i) - 'A'); } - public static Map loadResource(String res) - { + public static Map loadResource(String res) { return loadResource(ASMHelper.class.getResourceAsStream(res), res); } - - public static Map loadResource(InputStream in, String res) - { + + public static Map loadResource(InputStream in, String res) { HashMap blocks = new HashMap(); String current = "unnamed"; ASMBlock block = new ASMBlock(); - try - { + try { BufferedReader r = new BufferedReader(new InputStreamReader(in)); String line; - while((line = r.readLine()) != null) - { + while ((line = r.readLine()) != null) { { int hpos = line.indexOf('#'); - if(hpos >= 0) line = line.substring(0, hpos); + if (hpos >= 0) line = line.substring(0, hpos); } line = line.trim(); - if(line.length() == 0) continue; - if(line.startsWith("list ")) - { - if(block.insns.size() > 0) blocks.put(current, block); + if (line.length() == 0) continue; + if (line.startsWith("list ")) { + if (block.list.size() > 0) blocks.put(current, block); current = line.substring(5); block = new ASMBlock(); continue; } - - try - { + + try { AbstractInsnNode insn = null; String[] split = line.replace(" : ", ":").split(" "); Integer i_opcode = opCodes.get(split[0]); - if(i_opcode == null) - { - if(split[0].equals("LINENUMBER")) + if (i_opcode == null) { + if (split[0].equals("LINENUMBER")) insn = new LineNumberNode(Integer.parseInt(split[1]), block.getOrAdd(split[2])); - else if(split[0].startsWith("L")) + else if (split[0].startsWith("L")) insn = block.getOrAdd(split[0]); else throw new Exception("Unknown opcode " + split[0]); - } - else - { + } else { int opcode = i_opcode; - switch(TYPE[opcode]) - { + switch (TYPE[opcode]) { case INSN: insn = new InsnNode(opcode); - break; + break; case INT_INSN: insn = new IntInsnNode(opcode, Integer.parseInt(split[1])); - break; + break; case VAR_INSN: insn = new VarInsnNode(opcode, Integer.parseInt(split[1])); - break; + break; case TYPE_INSN: insn = new ObfMapping(split[1]).toClassloading().toInsn(opcode); - break; + break; case FIELD_INSN: case METHOD_INSN: StringBuilder sb = new StringBuilder(); - for(int i = 1; i < split.length; i++) + for (int i = 1; i < split.length; i++) sb.append(split[i]); insn = ObfMapping.fromDesc(sb.toString()).toClassloading().toInsn(opcode); - break; + break; case INVOKE_DYNAMIC_INSN: throw new Exception("Found INVOKEDYNAMIC while reading"); case JUMP_INSN: insn = new JumpInsnNode(opcode, block.getOrAdd(split[1])); - break; + break; case LDC_INSN: String cst = split[1]; - if(cst.contains("")) - insn = new LdcInsnNode(cst.substring(1, cst.length()-1)); - else if(cst.endsWith("L")) - insn = new LdcInsnNode(Long.valueOf(cst.substring(0, cst.length()-1))); - else if(cst.endsWith("F")) - insn = new LdcInsnNode(Float.valueOf(cst.substring(0, cst.length()-1))); - else if(cst.endsWith("D")) - insn = new LdcInsnNode(Double.valueOf(cst.substring(0, cst.length()-1))); + if(cst.equals("*")) + insn = new LdcInsnNode(null); + else if (cst.endsWith("\"")) + insn = new LdcInsnNode(cst.substring(1, cst.length() - 1)); + else if (cst.endsWith("L")) + insn = new LdcInsnNode(Long.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.endsWith("F")) + insn = new LdcInsnNode(Float.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.endsWith("D")) + insn = new LdcInsnNode(Double.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.contains(".")) + insn = new LdcInsnNode(Double.valueOf(cst)); else insn = new LdcInsnNode(Integer.valueOf(cst)); - break; + break; case IINC_INSN: insn = new IincInsnNode(opcode, Integer.parseInt(split[1])); - break; + break; case LABEL: throw new Exception("Use L# for labels"); case TABLESWITCH_INSN: @@ -326,29 +275,25 @@ public class ASMReader throw new Exception("I don't know how to deal with this insn type"); case MULTIANEWARRAY_INSN: insn = new MultiANewArrayInsnNode(split[1], Integer.parseInt(split[2])); - break; + break; case FRAME: throw new Exception("Use ClassWriter.COMPUTE_FRAMES"); } } - - if(insn != null) - block.insns.add(insn); - } - catch(Exception e) - { - System.err.println("Error while reading ASM Block "+ - current+" from "+res+", line: "+line); + + if (insn != null) + block.list.add(insn); + } catch (Exception e) { + System.err.println("Error while reading ASM Block " + + current + " from " + res + ", line: " + line); e.printStackTrace(); } } - + r.close(); - if(block.insns.size() > 0) blocks.put(current, block); - } - catch(IOException e) - { - throw new RuntimeException("Failed to read ASM resource: "+res, e); + if (block.list.size() > 0) blocks.put(current, block); + } catch (IOException e) { + throw new RuntimeException("Failed to read ASM resource: " + res, e); } return blocks; } diff --git a/src/codechicken/lib/asm/ImportantInsnVisitor.java b/src/codechicken/lib/asm/ImportantInsnVisitor.java new file mode 100644 index 0000000..6af770f --- /dev/null +++ b/src/codechicken/lib/asm/ImportantInsnVisitor.java @@ -0,0 +1,36 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +public class ImportantInsnVisitor extends ClassVisitor +{ + public class ImportantInsnMethodVisitor extends MethodVisitor + { + MethodVisitor delegate; + + public ImportantInsnMethodVisitor(int access, String name, String desc, String signature, String[] exceptions) { + super(Opcodes.ASM4, new MethodNode(access, name, desc, signature, exceptions)); + delegate = cv.visitMethod(access, name, desc, signature, exceptions); + } + + @Override + public void visitEnd() { + super.visitEnd(); + MethodNode mnode = (MethodNode)mv; + mnode.instructions = InsnComparator.getImportantList(mnode.instructions); + mnode.accept(delegate); + } + } + + public ImportantInsnVisitor(ClassVisitor cv) { + super(Opcodes.ASM4, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new ImportantInsnMethodVisitor(access, name, desc, signature, exceptions); + } +} diff --git a/src/codechicken/lib/asm/InsnComparator.java b/src/codechicken/lib/asm/InsnComparator.java new file mode 100644 index 0000000..5ec9cf0 --- /dev/null +++ b/src/codechicken/lib/asm/InsnComparator.java @@ -0,0 +1,189 @@ +package codechicken.lib.asm; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; +import org.objectweb.asm.tree.*; + +import java.util.*; + +import static org.objectweb.asm.tree.AbstractInsnNode.*; + +public class InsnComparator +{ + public static boolean varInsnEqual(VarInsnNode insn1, VarInsnNode insn2) { + return insn1.var == -1 || insn2.var == -1 || insn1.var == insn2.var; + } + + public static boolean methodInsnEqual(MethodInsnNode insn1, MethodInsnNode insn2) { + return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); + } + + public static boolean fieldInsnEqual(FieldInsnNode insn1, FieldInsnNode insn2) { + return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); + } + + public static boolean ldcInsnEqual(LdcInsnNode insn1, LdcInsnNode insn2) { + return insn1.cst == null || insn2.cst == null || insn1.cst.equals(insn2.cst); + } + + public static boolean typeInsnEqual(TypeInsnNode insn1, TypeInsnNode insn2) { + return insn1.desc.equals("*") || insn2.desc.equals("*") || insn1.desc.equals(insn2.desc); + } + + public static boolean iincInsnEqual(IincInsnNode node1, IincInsnNode node2) { + return node1.var == node2.var && node1.incr == node2.incr; + } + + public static boolean intInsnEqual(IntInsnNode node1, IntInsnNode node2) { + return node1.operand == -1 || node2.operand == -1 || node1.operand == node2.operand; + } + + public static boolean insnEqual(AbstractInsnNode node1, AbstractInsnNode node2) { + if (node1.getOpcode() != node2.getOpcode()) + return false; + + switch (node2.getType()) { + case VAR_INSN: + return varInsnEqual((VarInsnNode) node1, (VarInsnNode) node2); + case TYPE_INSN: + return typeInsnEqual((TypeInsnNode) node1, (TypeInsnNode) node2); + case FIELD_INSN: + return fieldInsnEqual((FieldInsnNode) node1, (FieldInsnNode) node2); + case METHOD_INSN: + return methodInsnEqual((MethodInsnNode) node1, (MethodInsnNode) node2); + case LDC_INSN: + return ldcInsnEqual((LdcInsnNode) node1, (LdcInsnNode) node2); + case IINC_INSN: + return iincInsnEqual((IincInsnNode) node1, (IincInsnNode) node2); + case INT_INSN: + return intInsnEqual((IntInsnNode) node1, (IntInsnNode) node2); + default: + return true; + } + } + + public static boolean insnImportant(AbstractInsnNode insn, Set controlFlowLabels) { + switch(insn.getType()) { + case LINE: + case FRAME: + return false; + case LABEL: + return controlFlowLabels.contains(insn); + default: + return true; + } + } + + public static Set getControlFlowLabels(InsnListSection list) { + return getControlFlowLabels(list.list); + } + + public static Set getControlFlowLabels(InsnList list) { + HashSet controlFlowLabels = new HashSet(); + for (AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) { + switch (insn.getType()) { + case JUMP_INSN: + JumpInsnNode jinsn = (JumpInsnNode) insn; + controlFlowLabels.add(jinsn.label); + break; + case TABLESWITCH_INSN: + TableSwitchInsnNode tsinsn = (TableSwitchInsnNode) insn; + controlFlowLabels.add(tsinsn.dflt); + for (LabelNode label : tsinsn.labels) + controlFlowLabels.add(label); + break; + case LOOKUPSWITCH_INSN: + LookupSwitchInsnNode lsinsn = (LookupSwitchInsnNode) insn; + controlFlowLabels.add(lsinsn.dflt); + for (LabelNode label : lsinsn.labels) + controlFlowLabels.add(label); + break; + } + } + return controlFlowLabels; + } + + public static InsnList getImportantList(InsnList list) { + return getImportantList(new InsnListSection(list)).list; + } + + public static InsnListSection getImportantList(InsnListSection list) { + if (list.size() == 0) + return list; + + Set controlFlowLabels = getControlFlowLabels(list); + Map labelMap = Maps.asMap(controlFlowLabels, new Function() + { + @Override + public LabelNode apply(LabelNode input) { + return input; + } + }); + + InsnListSection importantNodeList = new InsnListSection(); + for(AbstractInsnNode insn : list) + if (insnImportant(insn, controlFlowLabels)) + importantNodeList.add(insn.clone(labelMap)); + + return importantNodeList; + } + + public static List find(InsnListSection haystack, InsnListSection needle) { + Set controlFlowLabels = getControlFlowLabels(haystack); + LinkedList list = new LinkedList(); + for (int start = 0; start <= haystack.size() - needle.size(); start++) { + InsnListSection section = matches(haystack.drop(start), needle, controlFlowLabels); + if (section != null) { + list.add(section); + start = section.end-1; + } + } + + return list; + } + + public static List find(InsnList haystack, InsnListSection needle) { + return find(new InsnListSection(haystack), needle); + } + + public static InsnListSection matches(InsnListSection haystack, InsnListSection needle, Set controlFlowLabels) { + int h = 0, n = 0; + for (; h < haystack.size() && n < needle.size(); h++) { + AbstractInsnNode insn = haystack.get(h); + if(!insnImportant(insn, controlFlowLabels)) + continue; + + if (!insnEqual(haystack.get(h), needle.get(n))) + return null; + n++; + } + if (n != needle.size()) + return null; + + return haystack.take(h); + } + + public static InsnListSection findOnce(InsnListSection haystack, InsnListSection needle) { + List list = find(haystack, needle); + if(list.size() != 1) + throw new RuntimeException("Needle found " + list.size() + " times in Haystack:\n" + haystack + "\n\n" + needle); + + return list.get(0); + } + + public static InsnListSection findOnce(InsnList haystack, InsnListSection needle) { + return findOnce(new InsnListSection(haystack), needle); + } + + public static List findN(InsnListSection haystack, InsnListSection needle) { + List list = find(haystack, needle); + if(list.isEmpty()) + throw new RuntimeException("Needle not found in Haystack:\n" + haystack + "\n\n" + needle); + + return list; + } + + public static List findN(InsnList haystack, InsnListSection needle) { + return findN(new InsnListSection(haystack), needle); + } +} diff --git a/src/codechicken/lib/asm/InsnListPrinter.java b/src/codechicken/lib/asm/InsnListPrinter.java deleted file mode 100644 index 5c3c078..0000000 --- a/src/codechicken/lib/asm/InsnListPrinter.java +++ /dev/null @@ -1,197 +0,0 @@ -package codechicken.lib.asm; - -import codechicken.lib.asm.InstructionComparator.InsnListSection; -import org.objectweb.asm.Label; -import org.objectweb.asm.tree.*; -import org.objectweb.asm.util.Textifier; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.util.HashMap; - -public class InsnListPrinter extends Textifier -{ - private boolean buildingLabelMap = false; - - public void visitInsnList(InsnList list) - { - text.clear(); - if(labelNames == null) - labelNames = new HashMap(); - else - labelNames.clear(); - - buildingLabelMap = true; - for(AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) - if(insn.getType() == 8) - visitLabel(((LabelNode)insn).getLabel()); - - text.clear(); - buildingLabelMap = false; - - for(AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) - _visitInsn(insn); - } - - public void visitInsnList(InsnListSection subsection) - { - text.clear(); - if(labelNames == null) - labelNames = new HashMap(); - else - labelNames.clear(); - - buildingLabelMap = true; - { - AbstractInsnNode insn = subsection.first; - while(true) - { - if(insn.getType() == 8) - visitLabel(((LabelNode)insn).getLabel()); - if(insn == subsection.last) - break; - insn = insn.getNext(); - } - } - - text.clear(); - buildingLabelMap = false; - - { - AbstractInsnNode insn = subsection.first; - while(true) - { - _visitInsn(insn); - if(insn == subsection.last) - break; - insn = insn.getNext(); - } - } - } - - public void visitInsn(AbstractInsnNode insn) - { - text.clear(); - if(labelNames == null) - labelNames = new HashMap(); - else - labelNames.clear(); - - _visitInsn(insn); - } - - private void _visitInsn(AbstractInsnNode insn) - { - switch(insn.getType()) - { - case 0: - visitInsn(insn.getOpcode()); - break; - case 1: - IntInsnNode iinsn = (IntInsnNode)insn; - visitIntInsn(iinsn.getOpcode(), iinsn.operand); - break; - case 2: - VarInsnNode vinsn = (VarInsnNode)insn; - visitVarInsn(vinsn.getOpcode(), vinsn.var); - break; - case 3: - TypeInsnNode tinsn = (TypeInsnNode)insn; - visitTypeInsn(tinsn.getOpcode(), tinsn.desc); - break; - case 4: - FieldInsnNode finsn = (FieldInsnNode)insn; - visitFieldInsn(finsn.getOpcode(), finsn.owner, finsn.name, finsn.desc); - break; - case 5: - MethodInsnNode minsn = (MethodInsnNode)insn; - visitMethodInsn(minsn.getOpcode(), minsn.owner, minsn.name, minsn.desc); - break; - case 6: - InvokeDynamicInsnNode idinsn = (InvokeDynamicInsnNode)insn; - visitInvokeDynamicInsn(idinsn.name, idinsn.desc, idinsn.bsm, idinsn.bsmArgs); - break; - case 7: - JumpInsnNode jinsn = (JumpInsnNode)insn; - visitJumpInsn(jinsn.getOpcode(), jinsn.label.getLabel()); - break; - case 8: - LabelNode linsn = (LabelNode)insn; - visitLabel(linsn.getLabel()); - break; - case 9: - LdcInsnNode ldcinsn = (LdcInsnNode)insn; - visitLdcInsn(ldcinsn.cst); - break; - case 10: - IincInsnNode iiinsn = (IincInsnNode)insn; - visitIincInsn(iiinsn.var, iiinsn.incr); - break; - case 11: - TableSwitchInsnNode tsinsn = (TableSwitchInsnNode)insn; - Label[] tslables = new Label[tsinsn.labels.size()]; - for(int i = 0; i < tslables.length; i++) - tslables[i] = tsinsn.labels.get(i).getLabel(); - visitTableSwitchInsn(tsinsn.min, tsinsn.max, tsinsn.dflt.getLabel(), tslables); - break; - case 12: - LookupSwitchInsnNode lsinsn = (LookupSwitchInsnNode)insn; - Label[] lslables = new Label[lsinsn.labels.size()]; - for(int i = 0; i < lslables.length; i++) - lslables[i] = lsinsn.labels.get(i).getLabel(); - int[] lskeys = new int[lsinsn.keys.size()]; - for(int i = 0; i < lskeys.length; i++) - lskeys[i] = lsinsn.keys.get(i); - visitLookupSwitchInsn(lsinsn.dflt.getLabel(), lskeys, lslables); - break; - case 13: - MultiANewArrayInsnNode ainsn = (MultiANewArrayInsnNode)insn; - visitMultiANewArrayInsn(ainsn.desc, ainsn.dims); - break; - case 14: - FrameNode fnode = (FrameNode)insn; - switch(fnode.type) - { - case -1: - case 0: - visitFrame(fnode.type, fnode.local.size(), fnode.local.toArray(), fnode.stack.size(), fnode.stack.toArray()); - break; - case 1: - visitFrame(fnode.type, fnode.local.size(), fnode.local.toArray(), 0, null); - break; - case 2: - visitFrame(fnode.type, fnode.local.size(), null, 0, null); - break; - case 3: - visitFrame(fnode.type, 0, null, 0, null); - break; - case 4: - visitFrame(fnode.type, 0, null, 1, fnode.stack.toArray()); - } - break; - case 15: - LineNumberNode lnode = (LineNumberNode)insn; - visitLineNumber(lnode.line, lnode.start.getLabel()); - break; - } - } - - @Override - public void visitLabel(Label label) - { - if(!buildingLabelMap && !labelNames.containsKey(label)) - { - labelNames.put(label, "LEXT"+labelNames.size()); - } - super.visitLabel(label); - } - - public String textString() - { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - print(pw); - pw.flush(); - return sw.toString(); - } -} diff --git a/src/codechicken/lib/asm/InsnListSection.java b/src/codechicken/lib/asm/InsnListSection.java new file mode 100644 index 0000000..bdadda2 --- /dev/null +++ b/src/codechicken/lib/asm/InsnListSection.java @@ -0,0 +1,241 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.tree.*; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import static org.objectweb.asm.tree.AbstractInsnNode.*; + +/** + * A section of an InsnList, may become invalid if the insn list is modified + */ +public class InsnListSection implements Iterable +{ + private class InsnListSectionIterator implements Iterator + { + int i = 0; + + @Override + public boolean hasNext() { + return i < size(); + } + + @Override + public AbstractInsnNode next() { + return get(i++); + } + + @Override + public void remove() { + InsnListSection.this.remove(--i); + } + } + + public InsnList list; + public int start; + public int end; + + public InsnListSection(InsnList list, int start, int end) { + this.list = list; + this.start = start; + this.end = end; + } + + public InsnListSection(InsnList list, AbstractInsnNode first, AbstractInsnNode last) { + this(list, list.indexOf(first), list.indexOf(last)+1); + } + + public InsnListSection(InsnList list) { + this(list, 0, list.size()); + } + + public InsnListSection() { + this(new InsnList()); + } + + public void accept(MethodVisitor mv) { + for(AbstractInsnNode insn : this) + insn.accept(mv); + } + + public AbstractInsnNode getFirst() { + return size() == 0 ? null : list.get(start); + } + + public AbstractInsnNode getLast() { + return size() == 0 ? null : list.get(end - 1); + } + + public int size() { + return end - start; + } + + public AbstractInsnNode get(int i) { + return list.get(start + i); + } + + public void set(int i, AbstractInsnNode insn) { + list.set(get(i), insn); + } + + public void remove(int i) { + list.remove(get(i)); + end--; + } + + public void replace(AbstractInsnNode location, AbstractInsnNode insn) { + list.set(location, insn); + } + + public void add(AbstractInsnNode insn) { + list.add(insn); + end++; + } + + public void insertBefore(InsnList insns) { + int s = insns.size(); + if(this.list.size() == 0) + list.insert(insns); + else + list.insertBefore(list.get(start), insns); + start+=s; + end+=s; + } + + public void insert(InsnList insns) { + if(end == 0) + list.insert(insns); + else + list.insert(list.get(end-1), insns); + } + + public void replace(InsnList insns) { + int s = insns.size(); + remove(); + insert(insns); + end = start+s; + } + + public void remove() { + while(end != start) + remove(0); + } + + public void setLast(AbstractInsnNode last) { + end = list.indexOf(last)+1; + } + + public void setFirst(AbstractInsnNode first) { + start = list.indexOf(first); + } + + public InsnListSection drop(int n) { + return slice(n, size()); + } + + public InsnListSection take(int n) { + return slice(0, n); + } + + public InsnListSection slice(int start, int end) { + return new InsnListSection(list, this.start+start, this.start+end); + } + + /** + * Removes leading and trailing labels and line number nodes that don't affect control flow + * @return this + */ + public InsnListSection trim(Set controlFlowLabels) { + while(start < end && !InsnComparator.insnImportant(getFirst(), controlFlowLabels)) + start++; + + while(start < end && !InsnComparator.insnImportant(getLast(), controlFlowLabels)) + end--; + + return this; + } + + public String toString() { + Textifier t = new Textifier(); + accept(new TraceMethodVisitor(t)); + StringWriter sw = new StringWriter(); + t.print(new PrintWriter(sw)); + return sw.toString(); + } + + public void println() { + System.out.println(toString()); + } + + public HashMap identityLabelMap() { + HashMap labelMap = new HashMap(); + for (AbstractInsnNode insn : this) + switch(insn.getType()) { + case LABEL: + labelMap.put((LabelNode) insn, (LabelNode) insn); + break; + case JUMP_INSN: + labelMap.put(((JumpInsnNode)insn).label, ((JumpInsnNode)insn).label); + break; + case LOOKUPSWITCH_INSN: + LookupSwitchInsnNode linsn = (LookupSwitchInsnNode)insn; + labelMap.put(linsn.dflt, linsn.dflt); + for(LabelNode label : linsn.labels) + labelMap.put(label, label); + break; + case TABLESWITCH_INSN: + TableSwitchInsnNode tinsn = (TableSwitchInsnNode)insn; + labelMap.put(tinsn.dflt, tinsn.dflt); + for(LabelNode label : tinsn.labels) + labelMap.put(label, label); + break; + case FRAME: + FrameNode fnode = (FrameNode)insn; + if(fnode.local != null) + for(Object o : fnode.local) + if(o instanceof LabelNode) + labelMap.put((LabelNode) o, (LabelNode) o); + if(fnode.stack != null) + for(Object o : fnode.stack) + if(o instanceof LabelNode) + labelMap.put((LabelNode) o, (LabelNode) o); + break; + } + + return labelMap; + } + + public Map cloneLabels() { + Map labelMap = identityLabelMap(); + for(Entry entry : labelMap.entrySet()) + entry.setValue(new LabelNode()); + + return labelMap; + } + + public InsnListSection copy() { + return copy(cloneLabels()); + } + + public InsnListSection copy(Map labelMap) { + InsnListSection copy = new InsnListSection(); + for(AbstractInsnNode insn : this) + copy.add(insn.clone(labelMap)); + + return copy; + } + + @Override + public Iterator iterator() { + return new InsnListSectionIterator(); + } +} \ No newline at end of file diff --git a/src/codechicken/lib/asm/InstructionComparator.java b/src/codechicken/lib/asm/InstructionComparator.java deleted file mode 100644 index 9e516b1..0000000 --- a/src/codechicken/lib/asm/InstructionComparator.java +++ /dev/null @@ -1,239 +0,0 @@ -package codechicken.lib.asm; - -import org.objectweb.asm.tree.*; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; - -import static org.objectweb.asm.tree.AbstractInsnNode.*; - -public class InstructionComparator -{ - public static class InsnListSection - { - public InsnListSection(AbstractInsnNode first, AbstractInsnNode last) - { - this.first = first; - this.last = last; - } - - public InsnListSection(InsnList haystack, int start, int end) - { - this(haystack.get(start), haystack.get(end)); - } - - public AbstractInsnNode first; - public AbstractInsnNode last; - } - - public static boolean varInsnEqual(VarInsnNode insn1, VarInsnNode insn2) - { - if(insn1.var == -1 || insn2.var == -1) - return true; - - return insn1.var == insn2.var; - } - - public static boolean methodInsnEqual(AbstractInsnNode absnode, int Opcode, ObfMapping method) - { - if(!(absnode instanceof MethodInsnNode) || absnode.getOpcode() != Opcode) - return false; - - return method.matches((MethodInsnNode)absnode); - } - - public static boolean methodInsnEqual(MethodInsnNode insn1, MethodInsnNode insn2) - { - return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); - } - - public static boolean fieldInsnEqual(FieldInsnNode insn1, FieldInsnNode insn2) - { - return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); - } - - public static boolean ldcInsnEqual(LdcInsnNode insn1, LdcInsnNode insn2) - { - if(insn1.cst.equals("~") || insn2.cst.equals("~")) - return true; - - return insn1.cst.equals(insn2.cst); - } - - public static boolean typeInsnEqual(TypeInsnNode insn1, TypeInsnNode insn2) - { - if(insn1.desc.equals("~") || insn2.desc.equals("~")) - return true; - - return insn1.desc.equals(insn2.desc); - } - - public static boolean iincInsnEqual(IincInsnNode node1, IincInsnNode node2) - { - return node1.var == node2.var && node1.incr == node2.incr; - } - - public static boolean intInsnEqual(IntInsnNode node1, IntInsnNode node2) - { - if(node1.operand == -1 || node2.operand == -1) - return true; - - return node1.operand == node2.operand; - } - - public static boolean insnEqual(AbstractInsnNode node1, AbstractInsnNode node2) - { - if(node1.getOpcode() != node2.getOpcode()) - return false; - - switch(node2.getType()) - { - case VAR_INSN: - return varInsnEqual((VarInsnNode) node1, (VarInsnNode) node2); - case TYPE_INSN: - return typeInsnEqual((TypeInsnNode) node1, (TypeInsnNode) node2); - case FIELD_INSN: - return fieldInsnEqual((FieldInsnNode) node1, (FieldInsnNode) node2); - case METHOD_INSN: - return methodInsnEqual((MethodInsnNode) node1, (MethodInsnNode) node2); - case LDC_INSN: - return ldcInsnEqual((LdcInsnNode) node1, (LdcInsnNode) node2); - case IINC_INSN: - return iincInsnEqual((IincInsnNode) node1, (IincInsnNode) node2); - case INT_INSN: - return intInsnEqual((IntInsnNode)node1, (IntInsnNode)node2); - default: - return true; - } - } - - public static InsnList getImportantList(InsnList list) - { - if(list.size() == 0) - return list; - - HashMap labels = new HashMap(); - for(AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) - { - if(insn instanceof LabelNode) - labels.put((LabelNode)insn, (LabelNode)insn); - } - - InsnList importantNodeList = new InsnList(); - for(AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) - { - if(insn instanceof LabelNode || insn instanceof LineNumberNode) - continue; - - importantNodeList.add(insn.clone(labels)); - } - return importantNodeList; - } - - public static boolean insnListMatches(InsnList haystack, InsnList needle, int start) - { - if(haystack.size()-start < needle.size()) - return false; - - for(int i = 0; i < needle.size(); i++) - { - if(!insnEqual(haystack.get(i+start), needle.get(i))) - return false; - } - return true; - } - - public static List insnListFind(InsnList haystack, InsnList needle) - { - LinkedList list = new LinkedList(); - for(int start = 0; start <= haystack.size()-needle.size(); start++) - if(insnListMatches(haystack, needle, start)) - list.add(start); - - return list; - } - - public static List insnListFindStart(InsnList haystack, InsnList needle) - { - LinkedList callNodes = new LinkedList(); - for(int callPoint : insnListFind(haystack, needle)) - callNodes.add(haystack.get(callPoint)); - return callNodes; - } - - public static List insnListFindEnd(InsnList haystack, InsnList needle) - { - LinkedList callNodes = new LinkedList(); - for(int callPoint : insnListFind(haystack, needle)) - callNodes.add(haystack.get(callPoint+needle.size()-1)); - return callNodes; - } - - public static List insnListFindL(InsnList haystack, InsnList needle) - { - HashSet controlFlowLabels = new HashSet(); - - for(AbstractInsnNode insn = haystack.getFirst(); insn != null; insn = insn.getNext()) - { - switch(insn.getType()) - { - case 8: - case 15: - break; - case 7: - JumpInsnNode jinsn = (JumpInsnNode)insn; - controlFlowLabels.add(jinsn.label); - break; - case 11: - TableSwitchInsnNode tsinsn = (TableSwitchInsnNode)insn; - for(LabelNode label : tsinsn.labels) - controlFlowLabels.add(label); - break; - case 12: - LookupSwitchInsnNode lsinsn = (LookupSwitchInsnNode)insn; - for(LabelNode label : lsinsn.labels) - controlFlowLabels.add(label); - break; - } - } - - LinkedList list = new LinkedList(); - nextsection: for(int start = 0; start <= haystack.size()-needle.size(); start++) - { - InsnListSection section = insnListMatchesL(haystack, needle, start, controlFlowLabels); - if(section != null) - { - for(InsnListSection asection : list) - if(asection.last == section.last) - continue nextsection; - - list.add(section); - } - } - - return list; - } - - private static InsnListSection insnListMatchesL(InsnList haystack, InsnList needle, int start, HashSet controlFlowLabels) - { - int h = start, n = 0; - for(;h < haystack.size() && n < needle.size(); h++) - { - AbstractInsnNode insn = haystack.get(h); - if(insn.getType() == 15) - continue; - if(insn.getType() == 8 && !controlFlowLabels.contains(insn)) - continue; - - if(!insnEqual(haystack.get(h), needle.get(n))) - return null; - n++; - } - if(n != needle.size()) - return null; - - return new InsnListSection(haystack, start, h-1); - } -} diff --git a/src/codechicken/lib/asm/LocalVariablesSorterVisitor.java b/src/codechicken/lib/asm/LocalVariablesSorterVisitor.java new file mode 100644 index 0000000..e843da6 --- /dev/null +++ b/src/codechicken/lib/asm/LocalVariablesSorterVisitor.java @@ -0,0 +1,37 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.LocalVariablesSorter; + +import java.util.Set; + +public class LocalVariablesSorterVisitor extends ClassVisitor + { + public Set methods; + public String owner; + + public LocalVariablesSorterVisitor(Set methods, ClassVisitor cv) { + super(Opcodes.ASM4, cv); + this.methods = methods; + } + + public LocalVariablesSorterVisitor(ClassVisitor cv) { + this(null, cv); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + owner = name; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + return methods == null || methods.contains(new ObfMapping(owner, name, desc)) ? new LocalVariablesSorter(access, desc, mv) : mv; + } + } + + \ No newline at end of file diff --git a/src/codechicken/lib/asm/ModularASMTransformer.java b/src/codechicken/lib/asm/ModularASMTransformer.java new file mode 100644 index 0000000..39e0d4c --- /dev/null +++ b/src/codechicken/lib/asm/ModularASMTransformer.java @@ -0,0 +1,285 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.*; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; + +import java.io.File; +import java.util.*; + +import static codechicken.lib.asm.ASMHelper.*; + +public class ModularASMTransformer +{ + public static class ClassNodeTransformerList + { + List transformers = new LinkedList(); + HashSet methodsToSort = new HashSet(); + + public void add(ClassNodeTransformer t) { + transformers.add(t); + t.addMethodsToSort(methodsToSort); + } + + public byte[] transform(byte[] bytes) { + ClassNode cnode = new ClassNode(); + ClassReader reader = new ClassReader(bytes); + ClassVisitor cv = cnode; + if(!methodsToSort.isEmpty()) + cv = new LocalVariablesSorterVisitor(methodsToSort, cv); + reader.accept(cv, ClassReader.EXPAND_FRAMES); + + try { + int writeFlags = 0; + for(ClassNodeTransformer t : transformers) { + t.transform(cnode); + writeFlags|=t.writeFlags; + } + + bytes = createBytes(cnode, writeFlags); + if(config.getTag("dump_asm").getBooleanValue(true)) + dump(bytes, new File("asm/ccl_modular/"+cnode.name.replace('/', '#')+".txt"), false, false); + return bytes; + } catch (RuntimeException e) { + dump(bytes, new File("asm/ccl_modular/"+cnode.name.replace('/', '#')+".txt"), false, false); + throw e; + } + } + } + + public static abstract class ClassNodeTransformer + { + public int writeFlags; + public ClassNodeTransformer(int writeFlags) { + this.writeFlags = writeFlags; + } + + public ClassNodeTransformer() { + this(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + } + + public abstract String className(); + public abstract void transform(ClassNode cnode); + + public void addMethodsToSort(Set set) {} + } + + public static abstract class MethodTransformer extends ClassNodeTransformer + { + public final ObfMapping method; + + public MethodTransformer(ObfMapping method) { + this.method = method.toClassloading(); + } + + @Override + public String className() { + return method.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + MethodNode mv = findMethod(method, cnode); + if (mv == null) + throw new RuntimeException("Method not found: " + method); + + try { + transform(mv); + } catch (Exception e) { + throw new RuntimeException("Error transforming method: "+method, e); + } + } + + public abstract void transform(MethodNode mv); + } + + public static class MethodWriter extends ClassNodeTransformer + { + public final int access; + public final ObfMapping method; + public final String[] exceptions; + public InsnList list; + + public MethodWriter(int access, ObfMapping method) { + this(access, method, null, (InsnList)null); + } + + public MethodWriter(int access, ObfMapping method, InsnList list) { + this(access, method, null, list); + } + + public MethodWriter(int access, ObfMapping method, ASMBlock block) { + this(access, method, null, block); + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions) { + this(access, method, exceptions, (InsnList)null); + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions, InsnList list) { + this.access = access; + this.method = method.toClassloading(); + this.exceptions = exceptions; + this.list = list; + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions, ASMBlock block) { + this(access, method, exceptions, block.rawListCopy()); + } + + @Override + public String className() { + return method.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + MethodNode mv = findMethod(method, cnode); + if(mv == null) + mv = (MethodNode) method.visitMethod(cnode, access, exceptions); + else { + mv.access = access; + mv.instructions.clear(); + if (mv.localVariables != null) + mv.localVariables.clear(); + if (mv.tryCatchBlocks != null) + mv.tryCatchBlocks.clear(); + } + + write(mv); + } + + public void write(MethodNode mv) { + logger.debug("Writing method "+method); + list.accept(mv); + } + } + + public static class MethodInjector extends MethodTransformer + { + public ASMBlock needle; + public ASMBlock injection; + public boolean before; + + public MethodInjector(ObfMapping method, ASMBlock needle, ASMBlock injection, boolean before) { + super(method); + this.needle = needle; + this.injection = injection; + this.before = before; + } + + public MethodInjector(ObfMapping method, ASMBlock injection, boolean before) { + this(method, null, injection, before); + } + + public MethodInjector(ObfMapping method, InsnList needle, InsnList injection, boolean before) { + this(method, new ASMBlock(needle), new ASMBlock(injection), before); + } + + public MethodInjector(ObfMapping method, InsnList injection, boolean before) { + this(method, null, new ASMBlock(injection), before); + } + + @Override + public void addMethodsToSort(Set set) { + set.add(method); + } + + @Override + public void transform(MethodNode mv) { + if(needle == null) { + logger.debug("Injecting "+(before ? "before" : "after")+" method "+method); + if (before) + mv.instructions.insert(injection.rawListCopy()); + else + mv.instructions.add(injection.rawListCopy()); + } + else { + for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { + logger.debug("Injecting "+(before ? "before" : "after")+" method "+method+" @ "+key.start+" - "+key.end); + ASMBlock injectBlock = injection.copy().mergeLabels(needle.applyLabels(key)); + + if (before) + key.insertBefore(injectBlock.list.list); + else + key.insert(injectBlock.list.list); + } + } + } + } + + public static class MethodReplacer extends MethodTransformer + { + public ASMBlock needle; + public ASMBlock replacement; + + public MethodReplacer(ObfMapping method, ASMBlock needle, ASMBlock replacement) { + super(method); + this.needle = needle; + this.replacement = replacement; + } + + public MethodReplacer(ObfMapping method, InsnList needle, InsnList replacement) { + this(method, new ASMBlock(needle), new ASMBlock(replacement)); + } + + @Override + public void addMethodsToSort(Set set) { + set.add(method); + } + + @Override + public void transform(MethodNode mv) { + for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { + logger.debug("Replacing method "+method+" @ "+key.start+" - "+key.end); + ASMBlock replaceBlock = replacement.copy().pullLabels(needle.applyLabels(key)); + key.insert(replaceBlock.list.list); + } + } + } + + public static class FieldWriter extends ClassNodeTransformer + { + public final ObfMapping field; + public final int access; + public final Object value; + + public FieldWriter(int access, ObfMapping field, Object value) { + this.field = field.toClassloading(); + this.access = access; + this.value = value; + } + + public FieldWriter(int access, ObfMapping field) { + this(access, field, null); + } + + @Override + public String className() { + return field.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + field.visitField(cnode, access, value); + } + } + + public HashMap transformers = new HashMap(); + + public void add(ClassNodeTransformer t) { + ClassNodeTransformerList list = transformers.get(t.className()); + if(list == null) + transformers.put(t.className(), list = new ClassNodeTransformerList()); + list.add(t); + } + + public byte[] transform(String name, byte[] bytes) { + if(bytes == null) + return null; + + ClassNodeTransformerList list = transformers.get(name); + return list == null ? bytes : list.transform(bytes); + } +} diff --git a/src/codechicken/lib/asm/ObfMapping.java b/src/codechicken/lib/asm/ObfMapping.java index 3f8d85d..c7c5579 100644 --- a/src/codechicken/lib/asm/ObfMapping.java +++ b/src/codechicken/lib/asm/ObfMapping.java @@ -1,6 +1,5 @@ package codechicken.lib.asm; -import codechicken.lib.config.ConfigFile; import codechicken.lib.config.ConfigTag; import com.google.common.base.Charsets; import com.google.common.base.Objects; @@ -9,6 +8,9 @@ import com.google.common.io.Resources; import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; import cpw.mods.fml.relauncher.FMLInjectionData; import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.common.ForgeVersion; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.tree.*; @@ -74,14 +76,9 @@ public class ObfMapping public static class MCPRemapper extends Remapper implements LineProcessor { - public static ConfigFile config; - public static File[] getConfFiles() { - if (config == null) - config = new ConfigFile(new File((File) FMLInjectionData.data()[6], "config/CodeChickenLib.cfg")).setComment("CodeChickenLib configuration file."); - - ConfigTag tag = config.getTag("dev.mappingDir").setComment("Path to directory holding packaged.srg, fields.csv and methods.csv for mcp remapping"); - for (int i = 0; i < 6; i++) { + ConfigTag tag = ASMHelper.config.getTag("mappingDir").setComment("Path to directory holding packaged.srg, fields.csv and methods.csv for mcp remapping"); + for (int i = 0; i < DIR_GUESSES+DIR_ASKS; i++) { File dir = confDirectoryGuess(i, tag); if (dir == null || dir.isFile()) continue; @@ -90,7 +87,7 @@ public class ObfMapping try { mappings = parseConfDir(dir); } catch (Exception e) { - if (i >= 3) + if (i >= DIR_GUESSES) e.printStackTrace(); continue; } @@ -102,6 +99,8 @@ public class ObfMapping throw new RuntimeException("Failed to select mappings directory, set it manually in the config"); } + private static final int DIR_GUESSES = 4; + private static final int DIR_ASKS = 3; public static File confDirectoryGuess(int i, ConfigTag tag) { File mcDir = (File) FMLInjectionData.data()[6]; switch (i) { @@ -111,6 +110,9 @@ public class ObfMapping return new File(mcDir, "../conf"); case 2: return new File(mcDir, "../build/unpacked/conf"); + case 3: + return new File(System.getProperty("user.home"), ".gradle/caches/minecraft/net/minecraftforge/forge/"+ + FMLInjectionData.data()[4]+"-"+ ForgeVersion.getVersion()+"/unpacked/conf"); default: JFileChooser fc = new JFileChooser(mcDir); fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); @@ -281,6 +283,14 @@ public class ObfMapping mv.visitFieldInsn(opcode, s_owner, s_name, s_desc); } + public MethodVisitor visitMethod(ClassVisitor visitor, int access, String[] exceptions) { + return visitor.visitMethod(access, s_name, s_desc, null, exceptions); + } + + public FieldVisitor visitField(ClassVisitor visitor, int access, Object value) { + return visitor.visitField(access, s_name, s_desc, null, value); + } + public boolean isClass(String name) { return name.replace('.', '/').equals(s_owner); } diff --git a/src/codechicken/lib/config/ConfigFile.java b/src/codechicken/lib/config/ConfigFile.java index bddec27..b634dae 100644 --- a/src/codechicken/lib/config/ConfigFile.java +++ b/src/codechicken/lib/config/ConfigFile.java @@ -4,7 +4,19 @@ import java.io.*; public class ConfigFile extends ConfigTagParent { + public static final byte[] crlf = new byte[]{0xD, 0xA}; + + public File file; + private boolean loading; + public ConfigFile(File file) { + newlinemode = 2; + load(file); + } + + protected ConfigFile() {} + + protected void load(File file) { try { if(!file.getParentFile().exists()) file.getParentFile().mkdirs(); @@ -14,11 +26,10 @@ public class ConfigFile extends ConfigTagParent throw new RuntimeException(e); } this.file = file; - newlinemode = 2; loadConfig(); } - private void loadConfig() { + protected void loadConfig() { loading = true; BufferedReader reader; try { @@ -66,22 +77,7 @@ public class ConfigFile extends ConfigTagParent public static String readLine(BufferedReader reader) throws IOException { String line = reader.readLine(); - if (line != null) - return line.replace("\t", ""); - return line; - } - - public static String formatLine(String line) { - line = line.replace("\t", ""); - if (line.startsWith("#")) { - return line; - } else if (line.contains("=")) { - line = line.substring(0, line.indexOf("=")).replace(" ", "") + line.substring(line.indexOf("=")); - return line; - } else { - line = line.replace(" ", ""); - return line; - } + return line == null ? null : line.replace("\t", ""); } public static void writeLine(PrintWriter writer, String line, int tabs) { @@ -112,9 +108,4 @@ public class ConfigFile extends ConfigTagParent public boolean isLoading() { return loading; } - - public File file; - private boolean loading; - - public static final byte[] lineend = new byte[]{0xD, 0xA}; } diff --git a/src/codechicken/lib/config/ConfigTagParent.java b/src/codechicken/lib/config/ConfigTagParent.java index d293ad9..55bc009 100644 --- a/src/codechicken/lib/config/ConfigTagParent.java +++ b/src/codechicken/lib/config/ConfigTagParent.java @@ -182,7 +182,7 @@ public abstract class ConfigTagParent break; if(line.startsWith("#")) { - if(comment == null || comment.equals("")) + if(comment.equals("")) comment = line.substring(1); else comment = comment+"\n"+line.substring(1); diff --git a/src/codechicken/lib/config/DefaultingConfigFile.java b/src/codechicken/lib/config/DefaultingConfigFile.java new file mode 100644 index 0000000..27a7ef7 --- /dev/null +++ b/src/codechicken/lib/config/DefaultingConfigFile.java @@ -0,0 +1,18 @@ +package codechicken.lib.config; + +import java.io.File; + +public class DefaultingConfigFile extends ConfigFile +{ + public DefaultingConfigFile(File file) { + super(); + if(file.exists()) + load(file); + } + + @Override + public void saveConfig() { + if(file != null) + super.saveConfig(); + } +}