From d06b6211bc0815c36d44c65312c097058901b1c5 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 24 Jan 2024 17:16:50 +0100 Subject: chore: internal reorganisation (breaking) --- .../exceptions/InstructionMismatchException.java | 2 +- .../java/ftbsc/lll/proxies/impl/FieldProxy.java | 2 +- .../java/ftbsc/lll/proxies/impl/MethodProxy.java | 2 +- .../java/ftbsc/lll/proxies/impl/TypeProxy.java | 5 +- .../java/ftbsc/lll/tools/DescriptorBuilder.java | 141 ------------ src/main/java/ftbsc/lll/tools/InsnSequence.java | 138 ------------ src/main/java/ftbsc/lll/tools/PatternMatcher.java | 250 --------------------- src/main/java/ftbsc/lll/tools/StackTools.java | 86 ------- .../ftbsc/lll/tools/debug/BytecodePrinter.java | 79 ------- .../ftbsc/lll/tools/nodes/FieldProxyInsnNode.java | 22 -- .../ftbsc/lll/tools/nodes/MethodProxyInsnNode.java | 23 -- .../ftbsc/lll/tools/nodes/TypeProxyInsnNode.java | 23 -- .../java/ftbsc/lll/utils/DescriptorBuilder.java | 141 ++++++++++++ src/main/java/ftbsc/lll/utils/InsnSequence.java | 138 ++++++++++++ src/main/java/ftbsc/lll/utils/PatternMatcher.java | 250 +++++++++++++++++++++ src/main/java/ftbsc/lll/utils/StackUtils.java | 86 +++++++ .../ftbsc/lll/utils/debug/BytecodePrinter.java | 79 +++++++ .../ftbsc/lll/utils/nodes/FieldProxyInsnNode.java | 22 ++ .../ftbsc/lll/utils/nodes/MethodProxyInsnNode.java | 23 ++ .../ftbsc/lll/utils/nodes/TypeProxyInsnNode.java | 23 ++ 20 files changed, 767 insertions(+), 768 deletions(-) delete mode 100644 src/main/java/ftbsc/lll/tools/DescriptorBuilder.java delete mode 100644 src/main/java/ftbsc/lll/tools/InsnSequence.java delete mode 100644 src/main/java/ftbsc/lll/tools/PatternMatcher.java delete mode 100644 src/main/java/ftbsc/lll/tools/StackTools.java delete mode 100644 src/main/java/ftbsc/lll/tools/debug/BytecodePrinter.java delete mode 100644 src/main/java/ftbsc/lll/tools/nodes/FieldProxyInsnNode.java delete mode 100644 src/main/java/ftbsc/lll/tools/nodes/MethodProxyInsnNode.java delete mode 100644 src/main/java/ftbsc/lll/tools/nodes/TypeProxyInsnNode.java create mode 100644 src/main/java/ftbsc/lll/utils/DescriptorBuilder.java create mode 100644 src/main/java/ftbsc/lll/utils/InsnSequence.java create mode 100644 src/main/java/ftbsc/lll/utils/PatternMatcher.java create mode 100644 src/main/java/ftbsc/lll/utils/StackUtils.java create mode 100644 src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java create mode 100644 src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java create mode 100644 src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java create mode 100644 src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java diff --git a/src/main/java/ftbsc/lll/exceptions/InstructionMismatchException.java b/src/main/java/ftbsc/lll/exceptions/InstructionMismatchException.java index 33c4b35..ff04ef6 100644 --- a/src/main/java/ftbsc/lll/exceptions/InstructionMismatchException.java +++ b/src/main/java/ftbsc/lll/exceptions/InstructionMismatchException.java @@ -1,6 +1,6 @@ package ftbsc.lll.exceptions; -import ftbsc.lll.tools.InsnSequence; +import ftbsc.lll.utils.InsnSequence; /** * Thrown when attempting to build an {@link InsnSequence} between two diff --git a/src/main/java/ftbsc/lll/proxies/impl/FieldProxy.java b/src/main/java/ftbsc/lll/proxies/impl/FieldProxy.java index 62525bc..75b7fbc 100644 --- a/src/main/java/ftbsc/lll/proxies/impl/FieldProxy.java +++ b/src/main/java/ftbsc/lll/proxies/impl/FieldProxy.java @@ -7,7 +7,7 @@ import org.objectweb.asm.Type; import java.lang.reflect.Field; -import static ftbsc.lll.tools.DescriptorBuilder.nameToDescriptor; +import static ftbsc.lll.utils.DescriptorBuilder.nameToDescriptor; /** * A container for information about class fields to be used diff --git a/src/main/java/ftbsc/lll/proxies/impl/MethodProxy.java b/src/main/java/ftbsc/lll/proxies/impl/MethodProxy.java index a1306f7..d3f0ce3 100644 --- a/src/main/java/ftbsc/lll/proxies/impl/MethodProxy.java +++ b/src/main/java/ftbsc/lll/proxies/impl/MethodProxy.java @@ -10,7 +10,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static ftbsc.lll.tools.DescriptorBuilder.nameToDescriptor; +import static ftbsc.lll.utils.DescriptorBuilder.nameToDescriptor; /** * A container for information about class methods to be used diff --git a/src/main/java/ftbsc/lll/proxies/impl/TypeProxy.java b/src/main/java/ftbsc/lll/proxies/impl/TypeProxy.java index 022a286..680db9b 100644 --- a/src/main/java/ftbsc/lll/proxies/impl/TypeProxy.java +++ b/src/main/java/ftbsc/lll/proxies/impl/TypeProxy.java @@ -1,13 +1,12 @@ package ftbsc.lll.proxies.impl; -import ftbsc.lll.proxies.AbstractProxy; import ftbsc.lll.proxies.ProxyType; import ftbsc.lll.proxies.QualifiableProxy; import org.objectweb.asm.Type; import java.lang.reflect.Modifier; -import static ftbsc.lll.tools.DescriptorBuilder.nameToDescriptor; +import static ftbsc.lll.utils.DescriptorBuilder.nameToDescriptor; /** * A container for information about classes to be used @@ -123,4 +122,4 @@ public class TypeProxy extends QualifiableProxy { public boolean equals(Object obj) { return obj instanceof TypeProxy && super.equals(obj); } -} \ No newline at end of file +} diff --git a/src/main/java/ftbsc/lll/tools/DescriptorBuilder.java b/src/main/java/ftbsc/lll/tools/DescriptorBuilder.java deleted file mode 100644 index 1b33790..0000000 --- a/src/main/java/ftbsc/lll/tools/DescriptorBuilder.java +++ /dev/null @@ -1,141 +0,0 @@ -package ftbsc.lll.tools; - -import org.objectweb.asm.Type; - -import java.util.ArrayList; - -/** - * Builds a method descriptor for you. - * See the documentation to better understand what this is. - * Parameters must be given in a specific order. - * Return type should always be specified for clarity, but defaults to void. - */ -public class DescriptorBuilder { - /** - * The descriptor of the return type. - */ - private String returnType; - - /** - * The descriptors of the parameters. - */ - private final ArrayList params; - - /** - * Public constructor. - * Initialises default values. - */ - public DescriptorBuilder() { - this.returnType = Type.getDescriptor(void.class); - this.params = new ArrayList<>(); - } - - /** - * Sets the return type to the given type. - * Passing a {@link Class} may cause problems if used with objects outside the Java - * SDK. Pass the fully qualified name as a {@link String} rather than the {@link Class} - * object for non-standard types. - * @param returnType the Class object corresponding to the return type - * @return the builder's state after the change - */ - public DescriptorBuilder setReturnType(Class returnType) { - this.returnType = Type.getDescriptor(returnType); - return this; - } - - /** - * Sets the return type to the Object specified here as a fully - * qualified name. Example: java.lang.String. - * No validity checks are performed: it's up to the user to ensure the name is correct. - * @param returnType the fully qualified name of the desired Object. - * @return the builder's state after the change - */ - public DescriptorBuilder setReturnType(String returnType) { - return this.setReturnType(returnType, 0); - } - - /** - * Sets the return type to the Object specified here as a fully - * qualified name (example: java.lang.String), with the specified array level. - * No validity checks are performed: it's up to the user to ensure the name is correct. - * @param returnType the fully qualified name of the desired Object. - * @param arrayLevel how many levels of array are there - * (example: String is 0, String[] is 1, String[][] is 2, etc.) - * @return the builder's state after the change - */ - public DescriptorBuilder setReturnType(String returnType, int arrayLevel) { - this.returnType = nameToDescriptor(returnType, arrayLevel); - return this; - } - - /** - * Adds a parameter of the given class type to the method. - * Parameter order matters. - * Passing a {@link Class} may cause problems if used with objects outside the Java - * SDK. Pass the fully qualified name as a {@link String} rather than the {@link Class} - * object for non-standard types. - * @param param the Class object corresponding to the parameter - * @return the builder's state after the change - */ - public DescriptorBuilder addParameter(Class param) { - this.params.add(Type.getDescriptor(param)); - return this; - } - - /** - * Adds a parameter with the type specified by the given fully - * qualified name to the method. Example: java.lang.String. - * Parameter order matters. - * No validity checks are performed: it's up to the user to ensure the name is correct. - * @param param the fully qualified name of the parameter type - * @return the builder's state after the change - */ - public DescriptorBuilder addParameter(String param) { - return this.addParameter(param, 0); - } - - /** - * Adds a parameter with the type specified by the given fully - * qualified name (example: java.lang.String) to the method, with - * the specified array level. - * Parameter order matters. - * No validity checks are performed: it's up to the user to ensure the name is correct. - * @param param the fully qualified name of the parameter type - * @param arrayLevel how many levels of array are there - * (example: String is 0, String[] is 1, String[][] is 2, etc.) - * @return the builder's state after the change - */ - public DescriptorBuilder addParameter(String param, int arrayLevel) { - this.params.add(nameToDescriptor(param, arrayLevel)); - return this; - } - - /** - * Builds the descriptor into a string. - * Example result: {@code int m(Object[] o)} becomes {@code ([Ljava/lang/Object;)I} - * @return the resulting descriptor - */ - public String build() { - StringBuilder sb = new StringBuilder(); - sb.append('('); - for(String p : params) - sb.append(p); - sb.append(')').append(returnType); - return sb.toString(); - } - - /** - * Converts a fully qualified name and array level to a descriptor. - * @param name the fully qualified name of the object type - * @param arrayLevel how many levels of array are there - * (example: String is 0, String[] is 1, String[][] is 2, etc.) - * @return object descriptor - */ - public static String nameToDescriptor(String name, int arrayLevel) { - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < arrayLevel; i++) - sb.append('['); - sb.append('L').append(name.replace('.', '/')).append(';'); - return sb.toString(); - } -} diff --git a/src/main/java/ftbsc/lll/tools/InsnSequence.java b/src/main/java/ftbsc/lll/tools/InsnSequence.java deleted file mode 100644 index d00292e..0000000 --- a/src/main/java/ftbsc/lll/tools/InsnSequence.java +++ /dev/null @@ -1,138 +0,0 @@ -package ftbsc.lll.tools; - -import ftbsc.lll.exceptions.InstructionMismatchException; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.InsnList; - -import java.util.Objects; - -/** - * Represents a sequence of instructions contained within two given nodes. - * Extends {@link InsnList}, but provides additional flexibility and features. - */ -public class InsnSequence extends InsnList { - /** - * Public constructor. - * This creates an empty sequence. - */ - public InsnSequence() { - super(); - } - - /** - * Public constructor for list with single item. - * Must be given a single non-null node. - * @param node the node in question - */ - public InsnSequence(AbstractInsnNode node) { - super(); - this.add(node); - } - - /** - * Public constructor. - * Must be given two non-null, connected nodes. - * @param startNode the starting node of the pattern - * @param endNode the first node of the pattern - */ - public InsnSequence(AbstractInsnNode startNode, AbstractInsnNode endNode) { - Objects.requireNonNull(startNode); - Objects.requireNonNull(endNode); - for(; startNode != null; startNode = startNode.getNext()) { - this.add(startNode); - if(startNode == endNode) break; - } - if(startNode == null) - throw new InstructionMismatchException("Nodes" + getFirst() + " and " + getLast() + " are not connected."); - } - - /** - * Extends the existing get function from InsnList to allow for negative indexes. - * @param index the index of the instruction that must be returned - * @return the instruction whose index is given - */ - @Override - public AbstractInsnNode get(int index) { - if(index >= 0) - return super.get(index); - index = Math.abs(index); - if(index > size()) - throw new IndexOutOfBoundsException(); - return super.get(size() - index); - } - - /** - * Adds an array of nodes to the list. - * @param nodes the nodes to add - */ - public void add(AbstractInsnNode... nodes) { - for(AbstractInsnNode node : nodes) - this.add(node); - } - - /** - * Wraps InsnList's add() to throw an exception - * when given null values. - * @param node to add - */ - @Override - public void add(AbstractInsnNode node) { - Objects.requireNonNull(node); - super.add(node); - } - - /** - * Replaces a node with another one. Mostly used internally. - * @param oldNode node to replace - * @param newNode new node - */ - public void replaceNode(AbstractInsnNode oldNode, AbstractInsnNode newNode) { - super.insert(oldNode, newNode); - super.remove(oldNode); - } - - /** - * Replaces n occurrences of said opcode with the given node. - * @param opcode the opcode to replace - * @param newNode the replacement node - * @param amount how many occurrences to replace, set to 0 to replace all - * @return true if anything was changed, false otherwise - */ - public boolean replace(int opcode, AbstractInsnNode newNode, int amount) { - return replace(opcode, newNode, amount, false); - } - - /** - * Replaces n occurrences of said opcode with the given node. - * @param opcode the opcode to replace - * @param newNode the replacement node - * @param reverse whether the search should be done from the end - * @param amount how many occurrences to replace, set to 0 to replace all - * @return true if anything was changed, false otherwise - */ - public boolean replace(int opcode, AbstractInsnNode newNode, int amount, boolean reverse) { - boolean changed = false; - for(AbstractInsnNode cur = this.getFirst(); - cur != null && cur.getPrevious() != this.getLast() && cur.getNext() != this.getFirst(); - cur = reverse ? cur.getPrevious() : cur.getNext()) { - if(cur.getOpcode() == opcode) { - this.replaceNode(cur, newNode); - changed = true; - amount--; // will go to negative if it was already 0, causing it to go on until for loop finishes - if(amount == 0) - return changed; - } - } - return changed; - } - - /** - * Cut a number of nodes from the list. - * @param amount how many nodes to cut - * @param reverse true if should cut from the end, false otherwise - */ - public void cut(int amount, boolean reverse) { - for(int i = 0; i < amount; i++) - this.remove(reverse ? getLast() : getFirst()); - } -} diff --git a/src/main/java/ftbsc/lll/tools/PatternMatcher.java b/src/main/java/ftbsc/lll/tools/PatternMatcher.java deleted file mode 100644 index fff84db..0000000 --- a/src/main/java/ftbsc/lll/tools/PatternMatcher.java +++ /dev/null @@ -1,250 +0,0 @@ -package ftbsc.lll.tools; - -import ftbsc.lll.exceptions.PatternNotFoundException; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodNode; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - -/** - * Describes a pattern to match on a list of ASM instructions. - */ -public class PatternMatcher { - /** - * The list of predicates to match. - */ - private final List> predicates; - - /** - * Whether pattern search should be done from the end. - */ - private final boolean reverse; - - /** - * Patterns flagged with this ignore labels. - */ - private final boolean ignoreLabels; - - /** - * Patterns flagged with this ignore FRAME instructions. - */ - private final boolean ignoreFrames; - - /** - * Patterns flagged with this ignore LINENUMBER instructions. - */ - private final boolean ignoreLineNumbers; - - /** - * Private constructor because a PatternMatcher should only ever be initialized - * through the builder. - * @param predicates the list of predicates to match - * @param reverse search direction - * @param ignoreLabels whether LABEL instructions should be ignored - * @param ignoreFrames whether FRAME instructions should be ignored - * @param ignoreLineNumbers whether LINENUMBER instructions should be ignored - */ - private PatternMatcher(List> predicates, boolean reverse, - boolean ignoreLabels, boolean ignoreFrames, boolean ignoreLineNumbers) { - this.predicates = predicates; - this.reverse = reverse; - this.ignoreLabels = ignoreLabels; - this.ignoreFrames = ignoreFrames; - this.ignoreLineNumbers = ignoreLineNumbers; - } - - /** - * @return the Builder object for this {@link PatternMatcher} - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Tries to match the given pattern on a given {@link MethodNode}. - * @param node the {@link MethodNode} to search - * @return the InsnSequence object representing the matched pattern - */ - public InsnSequence find(MethodNode node) { - return find(reverse ? node.instructions.getLast() : node.instructions.getFirst()); - } - - /** - * Tries to match the given pattern starting from a given node. - * @param node the node to start the search on - * @return the {@link InsnSequence} object representing the matched pattern - */ - public InsnSequence find(AbstractInsnNode node) { - if(node != null) { - AbstractInsnNode first, last; - for(AbstractInsnNode cur = node; cur != null; cur = reverse ? cur.getPrevious() : cur.getNext()) { - if(predicates.size() == 0) return new InsnSequence(cur); //match whatever - first = cur; - last = cur; - for(int match = 0; last != null && match < predicates.size(); last = reverse ? last.getPrevious() : last.getNext()) { - if(match != 0) { - if(ignoreLabels && last.getType() == AbstractInsnNode.LABEL) continue; - if(ignoreFrames && last.getType() == AbstractInsnNode.FRAME) continue; - if(ignoreLineNumbers && last.getType() == AbstractInsnNode.LINE) continue; - } - if(!predicates.get(match).test(last)) break; - if(match == predicates.size() - 1) { - if(reverse) return new InsnSequence(last, first); //we are matching backwards - else return new InsnSequence(first, last); - } else match++; - } - } - } - throw new PatternNotFoundException("Failed to find pattern!"); - } - - /** - * The Builder object for {@link PatternMatcher}. - */ - public static class Builder { - - /** - * List of predicates the pattern has to match. - */ - private final List> predicates = new ArrayList<>(); - - /** - * Whether the pattern matching should proceed in reversed order. - */ - private boolean reverse = false; - - /** - * Patterns flagged with this ignore labels. - */ - private boolean ignoreLabels = false; - - /** - * Patterns flagged with this ignore FRAME instructions. - */ - private boolean ignoreFrames = false; - - /** - * Patterns flagged with this ignore LINENUMBER instructions. - */ - private boolean ignoreLineNumbers = false; - - /** - * Builds the pattern defined so far. - * @return the built {@link PatternMatcher} - */ - public PatternMatcher build() { - return new PatternMatcher(predicates, reverse, ignoreLabels, ignoreFrames, ignoreLineNumbers); - } - - /** - * Sets the pattern to match starting from the end. - * @return the builder's state after the operation - */ - public Builder reverse() { - this.reverse = true; - return this; - } - - /** - * Adds a custom predicate to the list. Also used internally. - * @param predicate the predicate to add - * @return the builder's state after the operation - */ - public Builder check(Predicate predicate) { - predicates.add(predicate); - return this; - } - - /** - * Wildcard, matches any kind of node. - * @return the builder's state after the operation - */ - public Builder any() { - return check(i -> true); - } - - /** - * Matches a specific opcode. - * @param opcode opcode to match - * @return the builder's state after the operation - */ - public Builder opcode(int opcode) { - return check(i -> i.getOpcode() == opcode); - } - - /** - * Matches a list of opcodes. - * @param opcodes list of opcodes to match - * @return the builder's state after the operation - */ - public Builder opcodes(int... opcodes) { - Builder res = this; - for(int o : opcodes) - res = opcode(o); - return res; - } - - /** - * Matches a method invokation of any kind: one of INVOKEVIRTUAL, - * INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. - * @return the builder's state after the operation - */ - public Builder method() { - return check(i -> i.getType() == AbstractInsnNode.METHOD_INSN); - } - - /** - * Matches a field invokation of any kind: one of GETSTATIC, PUTSTATIC, - * GETFIELD or PUTFIELD. - * @return the builder's state after the operation - */ - public Builder field() { - return check(i -> i.getType() == AbstractInsnNode.FIELD_INSN); - } - - /** - * Matches any kind of jump instruction. - * @return the builder's state after the operation - */ - public Builder jump() { - return check(i -> i.getType() == AbstractInsnNode.JUMP_INSN); - } - - /** - * Matches any kind of label. - * @return the builder's state after the operation - */ - public Builder label() { - return check(i -> i.getType() == AbstractInsnNode.LABEL); - } - - /** - * Tells the pattern matcher to ignore LABEL instructions. - * @return the builder's state after the operation - */ - public Builder ignoreLabels() { - this.ignoreLabels = true; - return this; - } - - /** - * Tells the pattern matcher to ignore FRAME instructions. - * @return the builder's state after the operation - */ - public Builder ignoreFrames() { - this.ignoreFrames = true; - return this; - } - - /** - * Tells the pattern matcher to ignore LINENUMBER instructions. - * @return the builder's state after the operation - */ - public Builder ignoreLineNumbers() { - this.ignoreLineNumbers = true; - return this; - } - } -} diff --git a/src/main/java/ftbsc/lll/tools/StackTools.java b/src/main/java/ftbsc/lll/tools/StackTools.java deleted file mode 100644 index f055ee2..0000000 --- a/src/main/java/ftbsc/lll/tools/StackTools.java +++ /dev/null @@ -1,86 +0,0 @@ -package ftbsc.lll.tools; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.*; - -import java.util.Comparator; - -/** - * Various methods for manipulating the stack. - * Includes anything from instantiation to variable manipulation - just about - * anything that loads stuff on or from the stack. - */ -public class StackTools implements Opcodes { - /** - * Creates a new instance of an object, given its internal name, constructor descriptor and instructions to load - * the parameters. - * The created object is loaded on the stack. - * @param name the internal name of the object to initialize, where - * the internal name of a class is its fully qualified name, where '.' are replaced by '/' - * @param desc the descriptor of the constructor to call - * @param args nodes containing instructions to load the constructor arguments, in the right order - * @return an instruction list containing the opcodes needed to create the new object and load it on the stack. - */ - public static InsnList instantiate(String name, String desc, AbstractInsnNode... args) { - InsnSequence is = new InsnSequence(); - is.add(args); - return instantiate(name, desc, is); - } - - /** - * Creates a new instance of an object, given its internal name, constructor descriptor and instructions to load - * the parameters. - * The created object is loaded on the stack. - * @param name the internal name of the object to initialize, where - * the internal name of a class is its fully qualified name, where '.' are replaced by '/' - * @param desc the descriptor of the constructor to call - * @param args a list of instructions loading the constructor arguments onto the stack in the correct order - * @return an instruction list containing the opcodes needed to create the new object and load it on the stack. - */ - public static InsnList instantiate(String name, String desc, InsnList args) { - InsnSequence list = new InsnSequence(); - list.add(new TypeInsnNode(NEW, name), new InsnNode(DUP)); - if(args != null) list.add(args); - list.add(new MethodInsnNode(INVOKESPECIAL, name, "", desc, false)); - return list; - } - - - /** - * Creates a new local variable, lasting in scope from the first to the last label of the given method. - * @param method the method for which to declare the local variable - * @param name the variable's name - * @param desc the type descriptor for the new variable - * @return the index value of the new local variable - */ - public static int addLocalVariable(MethodNode method, String name, String desc) { - return addLocalVariable( - method, name, desc, - (LabelNode) PatternMatcher.builder().label().build().find(method).getFirst(), - (LabelNode) PatternMatcher.builder().label().reverse().build().find(method).getFirst() - ); - } - - /** - * Creates a new local variable, lasting in scope between two given {@link LabelNode}s. - * @param method the method for which to declare the local variable - * @param name the variable's name - * @param desc the type descriptor for the new variable - * @param start the label at which the variable should enter scope - * @param end the label at which the variable should go out of scope - * @return the index value of the new local variable - */ - public static int addLocalVariable(MethodNode method, String name, String desc, LabelNode start, LabelNode end) { - final int targetIndex = - method.localVariables - .stream() - .max(Comparator.comparingInt(v -> v.index)) - .map(var -> var.desc.equals("J") || var.desc.equals("D") - ? var.index + 2 //skip two if long or double - major java moment - : var.index + 1) - .orElse(0); - LocalVariableNode variable = new LocalVariableNode(name, desc, null, start, end, targetIndex); - method.localVariables.add(variable); - return targetIndex; - } -} diff --git a/src/main/java/ftbsc/lll/tools/debug/BytecodePrinter.java b/src/main/java/ftbsc/lll/tools/debug/BytecodePrinter.java deleted file mode 100644 index b6dd8db..0000000 --- a/src/main/java/ftbsc/lll/tools/debug/BytecodePrinter.java +++ /dev/null @@ -1,79 +0,0 @@ -package ftbsc.lll.tools.debug; - -import org.apache.logging.log4j.Logger; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.util.Printer; -import org.objectweb.asm.util.Textifier; -import org.objectweb.asm.util.TraceMethodVisitor; - -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.nio.file.Files; -import java.nio.file.Paths; - -/** - * A collection of static methods for debugging by printing the ASM bytecode. - * These methods are only for debug, so most of the time they should stay unused. - */ -public class BytecodePrinter { - - /** - * Used for converting visit events to text, acts pretty much like a buffer in our case. - */ - private static final Printer PRINTER = new Textifier(); - - /** - * MethodVisitor that visits the method and prints it to a given printer. - */ - private static final TraceMethodVisitor MP = new TraceMethodVisitor(PRINTER); - - /** - * Prints the bytecode of a method using System.out.print(). - * @param main the method to print - */ - public static void printMethod(final MethodNode main) { - for (AbstractInsnNode i : main.instructions.toArray()) - System.out.print(insnToString(i)); - } - - /** - * Logs the bytecode of a method using the ASM logger. - * @param main the method to print - * @param logger the Log4j {@link Logger} to print it with - */ - public static void logMethod(final MethodNode main, final Logger logger) { - for (AbstractInsnNode i : main.instructions.toArray()) - logger.debug(insnToString(i)); - } - - /** - * Logs the bytecode of a method to a file. - * @param main the method to print - * @param path the file to log it to - */ - public static void logMethod(final MethodNode main, String path) { - StringBuilder out = new StringBuilder(); - for (AbstractInsnNode i : main.instructions.toArray()) - out.append(insnToString(i)); - try { - Files.write(Paths.get(path), out.toString().getBytes()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - /** - * Converts an instruction node to a String. - * @param insn the node to convert - * @return the converted string - */ - public static String insnToString(AbstractInsnNode insn) { - insn.accept(MP); - StringWriter sw = new StringWriter(); - PRINTER.print(new PrintWriter(sw)); - PRINTER.getText().clear(); - return sw.toString(); - } -} \ No newline at end of file diff --git a/src/main/java/ftbsc/lll/tools/nodes/FieldProxyInsnNode.java b/src/main/java/ftbsc/lll/tools/nodes/FieldProxyInsnNode.java deleted file mode 100644 index d1ac595..0000000 --- a/src/main/java/ftbsc/lll/tools/nodes/FieldProxyInsnNode.java +++ /dev/null @@ -1,22 +0,0 @@ -package ftbsc.lll.tools.nodes; - -import ftbsc.lll.proxies.impl.FieldProxy; -import org.objectweb.asm.tree.FieldInsnNode; - -/** - * Overrides the {@link FieldInsnNode} to add a constructor - * taking in a {@link FieldProxy}. - * @since 0.3.0 - */ -public class FieldProxyInsnNode extends FieldInsnNode { - /** - * Constructs a new {@link FieldInsnNode} starting - * from a {@link FieldProxy}. - * @param opcode the opcode, must be one of GETSTATIC, PUTSTATIC, - * GETFIELD or PUTFIELD - * @param f a {@link FieldProxy} representing the field to call - */ - public FieldProxyInsnNode(int opcode, FieldProxy f) { - super(opcode, f.parent.internalName, f.name, f.descriptor); - } -} diff --git a/src/main/java/ftbsc/lll/tools/nodes/MethodProxyInsnNode.java b/src/main/java/ftbsc/lll/tools/nodes/MethodProxyInsnNode.java deleted file mode 100644 index 73a26d7..0000000 --- a/src/main/java/ftbsc/lll/tools/nodes/MethodProxyInsnNode.java +++ /dev/null @@ -1,23 +0,0 @@ -package ftbsc.lll.tools.nodes; - -import ftbsc.lll.proxies.impl.MethodProxy; -import org.objectweb.asm.tree.MethodInsnNode; - -/** - * Overrides the {@link MethodInsnNode} to add a constructor - * taking in a {@link MethodProxy}. - * @since 0.3.0 - */ -public class MethodProxyInsnNode extends MethodInsnNode { - - /** - * Constructs a new {@link MethodInsnNode} starting - * from a {@link MethodProxy}. - * @param opcode the opcode, must be one of INVOKEVIRTUAL, - * INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE - * @param m a {@link MethodProxy} representing the method to call - */ - public MethodProxyInsnNode(int opcode, MethodProxy m) { - super(opcode, m.parent.internalName, m.name, m.descriptor); - } -} diff --git a/src/main/java/ftbsc/lll/tools/nodes/TypeProxyInsnNode.java b/src/main/java/ftbsc/lll/tools/nodes/TypeProxyInsnNode.java deleted file mode 100644 index 9e78dc9..0000000 --- a/src/main/java/ftbsc/lll/tools/nodes/TypeProxyInsnNode.java +++ /dev/null @@ -1,23 +0,0 @@ -package ftbsc.lll.tools.nodes; - -import ftbsc.lll.proxies.impl.TypeProxy; -import org.objectweb.asm.tree.TypeInsnNode; - -/** - * Overrides the {@link TypeInsnNode} to add a constructor - * taking in a {@link TypeProxy}. - * @since 0.4.0 - */ -public class TypeProxyInsnNode extends TypeInsnNode { - /** - * Constructs a new {@link TypeInsnNode} starting from a - * {@link TypeProxy}. The user should ensure that the TypeInsnNode - * represents a declared type before calling this. - * @param opcode the opcode, must be one of NEW, ANEWARRAY, - * CHECKCAST or INSTANCEOF - * @param t a {@link TypeProxy} representing the type to call - */ - public TypeProxyInsnNode(int opcode, TypeProxy t) { - super(opcode, t.internalName); - } -} diff --git a/src/main/java/ftbsc/lll/utils/DescriptorBuilder.java b/src/main/java/ftbsc/lll/utils/DescriptorBuilder.java new file mode 100644 index 0000000..541497b --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/DescriptorBuilder.java @@ -0,0 +1,141 @@ +package ftbsc.lll.utils; + +import org.objectweb.asm.Type; + +import java.util.ArrayList; + +/** + * Builds a method descriptor for you. + * See the documentation to better understand what this is. + * Parameters must be given in a specific order. + * Return type should always be specified for clarity, but defaults to void. + */ +public class DescriptorBuilder { + /** + * The descriptor of the return type. + */ + private String returnType; + + /** + * The descriptors of the parameters. + */ + private final ArrayList params; + + /** + * Public constructor. + * Initialises default values. + */ + public DescriptorBuilder() { + this.returnType = Type.getDescriptor(void.class); + this.params = new ArrayList<>(); + } + + /** + * Sets the return type to the given type. + * Passing a {@link Class} may cause problems if used with objects outside the Java + * SDK. Pass the fully qualified name as a {@link String} rather than the {@link Class} + * object for non-standard types. + * @param returnType the Class object corresponding to the return type + * @return the builder's state after the change + */ + public DescriptorBuilder setReturnType(Class returnType) { + this.returnType = Type.getDescriptor(returnType); + return this; + } + + /** + * Sets the return type to the Object specified here as a fully + * qualified name. Example: java.lang.String. + * No validity checks are performed: it's up to the user to ensure the name is correct. + * @param returnType the fully qualified name of the desired Object. + * @return the builder's state after the change + */ + public DescriptorBuilder setReturnType(String returnType) { + return this.setReturnType(returnType, 0); + } + + /** + * Sets the return type to the Object specified here as a fully + * qualified name (example: java.lang.String), with the specified array level. + * No validity checks are performed: it's up to the user to ensure the name is correct. + * @param returnType the fully qualified name of the desired Object. + * @param arrayLevel how many levels of array are there + * (example: String is 0, String[] is 1, String[][] is 2, etc.) + * @return the builder's state after the change + */ + public DescriptorBuilder setReturnType(String returnType, int arrayLevel) { + this.returnType = nameToDescriptor(returnType, arrayLevel); + return this; + } + + /** + * Adds a parameter of the given class type to the method. + * Parameter order matters. + * Passing a {@link Class} may cause problems if used with objects outside the Java + * SDK. Pass the fully qualified name as a {@link String} rather than the {@link Class} + * object for non-standard types. + * @param param the Class object corresponding to the parameter + * @return the builder's state after the change + */ + public DescriptorBuilder addParameter(Class param) { + this.params.add(Type.getDescriptor(param)); + return this; + } + + /** + * Adds a parameter with the type specified by the given fully + * qualified name to the method. Example: java.lang.String. + * Parameter order matters. + * No validity checks are performed: it's up to the user to ensure the name is correct. + * @param param the fully qualified name of the parameter type + * @return the builder's state after the change + */ + public DescriptorBuilder addParameter(String param) { + return this.addParameter(param, 0); + } + + /** + * Adds a parameter with the type specified by the given fully + * qualified name (example: java.lang.String) to the method, with + * the specified array level. + * Parameter order matters. + * No validity checks are performed: it's up to the user to ensure the name is correct. + * @param param the fully qualified name of the parameter type + * @param arrayLevel how many levels of array are there + * (example: String is 0, String[] is 1, String[][] is 2, etc.) + * @return the builder's state after the change + */ + public DescriptorBuilder addParameter(String param, int arrayLevel) { + this.params.add(nameToDescriptor(param, arrayLevel)); + return this; + } + + /** + * Builds the descriptor into a string. + * Example result: {@code int m(Object[] o)} becomes {@code ([Ljava/lang/Object;)I} + * @return the resulting descriptor + */ + public String build() { + StringBuilder sb = new StringBuilder(); + sb.append('('); + for(String p : params) + sb.append(p); + sb.append(')').append(returnType); + return sb.toString(); + } + + /** + * Converts a fully qualified name and array level to a descriptor. + * @param name the fully qualified name of the object type + * @param arrayLevel how many levels of array are there + * (example: String is 0, String[] is 1, String[][] is 2, etc.) + * @return object descriptor + */ + public static String nameToDescriptor(String name, int arrayLevel) { + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < arrayLevel; i++) + sb.append('['); + sb.append('L').append(name.replace('.', '/')).append(';'); + return sb.toString(); + } +} diff --git a/src/main/java/ftbsc/lll/utils/InsnSequence.java b/src/main/java/ftbsc/lll/utils/InsnSequence.java new file mode 100644 index 0000000..4734505 --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/InsnSequence.java @@ -0,0 +1,138 @@ +package ftbsc.lll.utils; + +import ftbsc.lll.exceptions.InstructionMismatchException; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; + +import java.util.Objects; + +/** + * Represents a sequence of instructions contained within two given nodes. + * Extends {@link InsnList}, but provides additional flexibility and features. + */ +public class InsnSequence extends InsnList { + /** + * Public constructor. + * This creates an empty sequence. + */ + public InsnSequence() { + super(); + } + + /** + * Public constructor for list with single item. + * Must be given a single non-null node. + * @param node the node in question + */ + public InsnSequence(AbstractInsnNode node) { + super(); + this.add(node); + } + + /** + * Public constructor. + * Must be given two non-null, connected nodes. + * @param startNode the starting node of the pattern + * @param endNode the first node of the pattern + */ + public InsnSequence(AbstractInsnNode startNode, AbstractInsnNode endNode) { + Objects.requireNonNull(startNode); + Objects.requireNonNull(endNode); + for(; startNode != null; startNode = startNode.getNext()) { + this.add(startNode); + if(startNode == endNode) break; + } + if(startNode == null) + throw new InstructionMismatchException("Nodes" + getFirst() + " and " + getLast() + " are not connected."); + } + + /** + * Extends the existing get function from InsnList to allow for negative indexes. + * @param index the index of the instruction that must be returned + * @return the instruction whose index is given + */ + @Override + public AbstractInsnNode get(int index) { + if(index >= 0) + return super.get(index); + index = Math.abs(index); + if(index > size()) + throw new IndexOutOfBoundsException(); + return super.get(size() - index); + } + + /** + * Adds an array of nodes to the list. + * @param nodes the nodes to add + */ + public void add(AbstractInsnNode... nodes) { + for(AbstractInsnNode node : nodes) + this.add(node); + } + + /** + * Wraps InsnList's add() to throw an exception + * when given null values. + * @param node to add + */ + @Override + public void add(AbstractInsnNode node) { + Objects.requireNonNull(node); + super.add(node); + } + + /** + * Replaces a node with another one. Mostly used internally. + * @param oldNode node to replace + * @param newNode new node + */ + public void replaceNode(AbstractInsnNode oldNode, AbstractInsnNode newNode) { + super.insert(oldNode, newNode); + super.remove(oldNode); + } + + /** + * Replaces n occurrences of said opcode with the given node. + * @param opcode the opcode to replace + * @param newNode the replacement node + * @param amount how many occurrences to replace, set to 0 to replace all + * @return true if anything was changed, false otherwise + */ + public boolean replace(int opcode, AbstractInsnNode newNode, int amount) { + return replace(opcode, newNode, amount, false); + } + + /** + * Replaces n occurrences of said opcode with the given node. + * @param opcode the opcode to replace + * @param newNode the replacement node + * @param reverse whether the search should be done from the end + * @param amount how many occurrences to replace, set to 0 to replace all + * @return true if anything was changed, false otherwise + */ + public boolean replace(int opcode, AbstractInsnNode newNode, int amount, boolean reverse) { + boolean changed = false; + for(AbstractInsnNode cur = this.getFirst(); + cur != null && cur.getPrevious() != this.getLast() && cur.getNext() != this.getFirst(); + cur = reverse ? cur.getPrevious() : cur.getNext()) { + if(cur.getOpcode() == opcode) { + this.replaceNode(cur, newNode); + changed = true; + amount--; // will go to negative if it was already 0, causing it to go on until for loop finishes + if(amount == 0) + return changed; + } + } + return changed; + } + + /** + * Cut a number of nodes from the list. + * @param amount how many nodes to cut + * @param reverse true if should cut from the end, false otherwise + */ + public void cut(int amount, boolean reverse) { + for(int i = 0; i < amount; i++) + this.remove(reverse ? getLast() : getFirst()); + } +} diff --git a/src/main/java/ftbsc/lll/utils/PatternMatcher.java b/src/main/java/ftbsc/lll/utils/PatternMatcher.java new file mode 100644 index 0000000..9518749 --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/PatternMatcher.java @@ -0,0 +1,250 @@ +package ftbsc.lll.utils; + +import ftbsc.lll.exceptions.PatternNotFoundException; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + +/** + * Describes a pattern to match on a list of ASM instructions. + */ +public class PatternMatcher { + /** + * The list of predicates to match. + */ + private final List> predicates; + + /** + * Whether pattern search should be done from the end. + */ + private final boolean reverse; + + /** + * Patterns flagged with this ignore labels. + */ + private final boolean ignoreLabels; + + /** + * Patterns flagged with this ignore FRAME instructions. + */ + private final boolean ignoreFrames; + + /** + * Patterns flagged with this ignore LINENUMBER instructions. + */ + private final boolean ignoreLineNumbers; + + /** + * Private constructor because a PatternMatcher should only ever be initialized + * through the builder. + * @param predicates the list of predicates to match + * @param reverse search direction + * @param ignoreLabels whether LABEL instructions should be ignored + * @param ignoreFrames whether FRAME instructions should be ignored + * @param ignoreLineNumbers whether LINENUMBER instructions should be ignored + */ + private PatternMatcher(List> predicates, boolean reverse, + boolean ignoreLabels, boolean ignoreFrames, boolean ignoreLineNumbers) { + this.predicates = predicates; + this.reverse = reverse; + this.ignoreLabels = ignoreLabels; + this.ignoreFrames = ignoreFrames; + this.ignoreLineNumbers = ignoreLineNumbers; + } + + /** + * @return the Builder object for this {@link PatternMatcher} + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Tries to match the given pattern on a given {@link MethodNode}. + * @param node the {@link MethodNode} to search + * @return the InsnSequence object representing the matched pattern + */ + public InsnSequence find(MethodNode node) { + return find(reverse ? node.instructions.getLast() : node.instructions.getFirst()); + } + + /** + * Tries to match the given pattern starting from a given node. + * @param node the node to start the search on + * @return the {@link InsnSequence} object representing the matched pattern + */ + public InsnSequence find(AbstractInsnNode node) { + if(node != null) { + AbstractInsnNode first, last; + for(AbstractInsnNode cur = node; cur != null; cur = reverse ? cur.getPrevious() : cur.getNext()) { + if(predicates.size() == 0) return new InsnSequence(cur); //match whatever + first = cur; + last = cur; + for(int match = 0; last != null && match < predicates.size(); last = reverse ? last.getPrevious() : last.getNext()) { + if(match != 0) { + if(ignoreLabels && last.getType() == AbstractInsnNode.LABEL) continue; + if(ignoreFrames && last.getType() == AbstractInsnNode.FRAME) continue; + if(ignoreLineNumbers && last.getType() == AbstractInsnNode.LINE) continue; + } + if(!predicates.get(match).test(last)) break; + if(match == predicates.size() - 1) { + if(reverse) return new InsnSequence(last, first); //we are matching backwards + else return new InsnSequence(first, last); + } else match++; + } + } + } + throw new PatternNotFoundException("Failed to find pattern!"); + } + + /** + * The Builder object for {@link PatternMatcher}. + */ + public static class Builder { + + /** + * List of predicates the pattern has to match. + */ + private final List> predicates = new ArrayList<>(); + + /** + * Whether the pattern matching should proceed in reversed order. + */ + private boolean reverse = false; + + /** + * Patterns flagged with this ignore labels. + */ + private boolean ignoreLabels = false; + + /** + * Patterns flagged with this ignore FRAME instructions. + */ + private boolean ignoreFrames = false; + + /** + * Patterns flagged with this ignore LINENUMBER instructions. + */ + private boolean ignoreLineNumbers = false; + + /** + * Builds the pattern defined so far. + * @return the built {@link PatternMatcher} + */ + public PatternMatcher build() { + return new PatternMatcher(predicates, reverse, ignoreLabels, ignoreFrames, ignoreLineNumbers); + } + + /** + * Sets the pattern to match starting from the end. + * @return the builder's state after the operation + */ + public Builder reverse() { + this.reverse = true; + return this; + } + + /** + * Adds a custom predicate to the list. Also used internally. + * @param predicate the predicate to add + * @return the builder's state after the operation + */ + public Builder check(Predicate predicate) { + predicates.add(predicate); + return this; + } + + /** + * Wildcard, matches any kind of node. + * @return the builder's state after the operation + */ + public Builder any() { + return check(i -> true); + } + + /** + * Matches a specific opcode. + * @param opcode opcode to match + * @return the builder's state after the operation + */ + public Builder opcode(int opcode) { + return check(i -> i.getOpcode() == opcode); + } + + /** + * Matches a list of opcodes. + * @param opcodes list of opcodes to match + * @return the builder's state after the operation + */ + public Builder opcodes(int... opcodes) { + Builder res = this; + for(int o : opcodes) + res = opcode(o); + return res; + } + + /** + * Matches a method invokation of any kind: one of INVOKEVIRTUAL, + * INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE. + * @return the builder's state after the operation + */ + public Builder method() { + return check(i -> i.getType() == AbstractInsnNode.METHOD_INSN); + } + + /** + * Matches a field invokation of any kind: one of GETSTATIC, PUTSTATIC, + * GETFIELD or PUTFIELD. + * @return the builder's state after the operation + */ + public Builder field() { + return check(i -> i.getType() == AbstractInsnNode.FIELD_INSN); + } + + /** + * Matches any kind of jump instruction. + * @return the builder's state after the operation + */ + public Builder jump() { + return check(i -> i.getType() == AbstractInsnNode.JUMP_INSN); + } + + /** + * Matches any kind of label. + * @return the builder's state after the operation + */ + public Builder label() { + return check(i -> i.getType() == AbstractInsnNode.LABEL); + } + + /** + * Tells the pattern matcher to ignore LABEL instructions. + * @return the builder's state after the operation + */ + public Builder ignoreLabels() { + this.ignoreLabels = true; + return this; + } + + /** + * Tells the pattern matcher to ignore FRAME instructions. + * @return the builder's state after the operation + */ + public Builder ignoreFrames() { + this.ignoreFrames = true; + return this; + } + + /** + * Tells the pattern matcher to ignore LINENUMBER instructions. + * @return the builder's state after the operation + */ + public Builder ignoreLineNumbers() { + this.ignoreLineNumbers = true; + return this; + } + } +} diff --git a/src/main/java/ftbsc/lll/utils/StackUtils.java b/src/main/java/ftbsc/lll/utils/StackUtils.java new file mode 100644 index 0000000..225b1ee --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/StackUtils.java @@ -0,0 +1,86 @@ +package ftbsc.lll.utils; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.*; + +import java.util.Comparator; + +/** + * Various methods for manipulating the stack. + * Includes anything from instantiation to variable manipulation - just about + * anything that loads stuff on or from the stack. + */ +public class StackUtils implements Opcodes { + /** + * Creates a new instance of an object, given its internal name, constructor descriptor and instructions to load + * the parameters. + * The created object is loaded on the stack. + * @param name the internal name of the object to initialize, where + * the internal name of a class is its fully qualified name, where '.' are replaced by '/' + * @param desc the descriptor of the constructor to call + * @param args nodes containing instructions to load the constructor arguments, in the right order + * @return an instruction list containing the opcodes needed to create the new object and load it on the stack. + */ + public static InsnList instantiate(String name, String desc, AbstractInsnNode... args) { + InsnSequence is = new InsnSequence(); + is.add(args); + return instantiate(name, desc, is); + } + + /** + * Creates a new instance of an object, given its internal name, constructor descriptor and instructions to load + * the parameters. + * The created object is loaded on the stack. + * @param name the internal name of the object to initialize, where + * the internal name of a class is its fully qualified name, where '.' are replaced by '/' + * @param desc the descriptor of the constructor to call + * @param args a list of instructions loading the constructor arguments onto the stack in the correct order + * @return an instruction list containing the opcodes needed to create the new object and load it on the stack. + */ + public static InsnList instantiate(String name, String desc, InsnList args) { + InsnSequence list = new InsnSequence(); + list.add(new TypeInsnNode(NEW, name), new InsnNode(DUP)); + if(args != null) list.add(args); + list.add(new MethodInsnNode(INVOKESPECIAL, name, "", desc, false)); + return list; + } + + + /** + * Creates a new local variable, lasting in scope from the first to the last label of the given method. + * @param method the method for which to declare the local variable + * @param name the variable's name + * @param desc the type descriptor for the new variable + * @return the index value of the new local variable + */ + public static int addLocalVariable(MethodNode method, String name, String desc) { + return addLocalVariable( + method, name, desc, + (LabelNode) PatternMatcher.builder().label().build().find(method).getFirst(), + (LabelNode) PatternMatcher.builder().label().reverse().build().find(method).getFirst() + ); + } + + /** + * Creates a new local variable, lasting in scope between two given {@link LabelNode}s. + * @param method the method for which to declare the local variable + * @param name the variable's name + * @param desc the type descriptor for the new variable + * @param start the label at which the variable should enter scope + * @param end the label at which the variable should go out of scope + * @return the index value of the new local variable + */ + public static int addLocalVariable(MethodNode method, String name, String desc, LabelNode start, LabelNode end) { + final int targetIndex = + method.localVariables + .stream() + .max(Comparator.comparingInt(v -> v.index)) + .map(var -> var.desc.equals("J") || var.desc.equals("D") + ? var.index + 2 //skip two if long or double - major java moment + : var.index + 1) + .orElse(0); + LocalVariableNode variable = new LocalVariableNode(name, desc, null, start, end, targetIndex); + method.localVariables.add(variable); + return targetIndex; + } +} diff --git a/src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java b/src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java new file mode 100644 index 0000000..c58fba4 --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/debug/BytecodePrinter.java @@ -0,0 +1,79 @@ +package ftbsc.lll.utils.debug; + +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.util.Printer; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * A collection of static methods for debugging by printing the ASM bytecode. + * These methods are only for debug, so most of the time they should stay unused. + */ +public class BytecodePrinter { + + /** + * Used for converting visit events to text, acts pretty much like a buffer in our case. + */ + private static final Printer PRINTER = new Textifier(); + + /** + * MethodVisitor that visits the method and prints it to a given printer. + */ + private static final TraceMethodVisitor MP = new TraceMethodVisitor(PRINTER); + + /** + * Prints the bytecode of a method using System.out.print(). + * @param main the method to print + */ + public static void printMethod(final MethodNode main) { + for (AbstractInsnNode i : main.instructions.toArray()) + System.out.print(insnToString(i)); + } + + /** + * Logs the bytecode of a method using the ASM logger. + * @param main the method to print + * @param logger the Log4j {@link Logger} to print it with + */ + public static void logMethod(final MethodNode main, final Logger logger) { + for (AbstractInsnNode i : main.instructions.toArray()) + logger.debug(insnToString(i)); + } + + /** + * Logs the bytecode of a method to a file. + * @param main the method to print + * @param path the file to log it to + */ + public static void logMethod(final MethodNode main, String path) { + StringBuilder out = new StringBuilder(); + for (AbstractInsnNode i : main.instructions.toArray()) + out.append(insnToString(i)); + try { + Files.write(Paths.get(path), out.toString().getBytes()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Converts an instruction node to a String. + * @param insn the node to convert + * @return the converted string + */ + public static String insnToString(AbstractInsnNode insn) { + insn.accept(MP); + StringWriter sw = new StringWriter(); + PRINTER.print(new PrintWriter(sw)); + PRINTER.getText().clear(); + return sw.toString(); + } +} diff --git a/src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java b/src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java new file mode 100644 index 0000000..e809ccd --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/nodes/FieldProxyInsnNode.java @@ -0,0 +1,22 @@ +package ftbsc.lll.utils.nodes; + +import ftbsc.lll.proxies.impl.FieldProxy; +import org.objectweb.asm.tree.FieldInsnNode; + +/** + * Overrides the {@link FieldInsnNode} to add a constructor + * taking in a {@link FieldProxy}. + * @since 0.3.0 + */ +public class FieldProxyInsnNode extends FieldInsnNode { + /** + * Constructs a new {@link FieldInsnNode} starting + * from a {@link FieldProxy}. + * @param opcode the opcode, must be one of GETSTATIC, PUTSTATIC, + * GETFIELD or PUTFIELD + * @param f a {@link FieldProxy} representing the field to call + */ + public FieldProxyInsnNode(int opcode, FieldProxy f) { + super(opcode, f.parent.internalName, f.name, f.descriptor); + } +} diff --git a/src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java b/src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java new file mode 100644 index 0000000..2e062f4 --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/nodes/MethodProxyInsnNode.java @@ -0,0 +1,23 @@ +package ftbsc.lll.utils.nodes; + +import ftbsc.lll.proxies.impl.MethodProxy; +import org.objectweb.asm.tree.MethodInsnNode; + +/** + * Overrides the {@link MethodInsnNode} to add a constructor + * taking in a {@link MethodProxy}. + * @since 0.3.0 + */ +public class MethodProxyInsnNode extends MethodInsnNode { + + /** + * Constructs a new {@link MethodInsnNode} starting + * from a {@link MethodProxy}. + * @param opcode the opcode, must be one of INVOKEVIRTUAL, + * INVOKESPECIAL, INVOKESTATIC or INVOKEINTERFACE + * @param m a {@link MethodProxy} representing the method to call + */ + public MethodProxyInsnNode(int opcode, MethodProxy m) { + super(opcode, m.parent.internalName, m.name, m.descriptor); + } +} diff --git a/src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java b/src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java new file mode 100644 index 0000000..3648278 --- /dev/null +++ b/src/main/java/ftbsc/lll/utils/nodes/TypeProxyInsnNode.java @@ -0,0 +1,23 @@ +package ftbsc.lll.utils.nodes; + +import ftbsc.lll.proxies.impl.TypeProxy; +import org.objectweb.asm.tree.TypeInsnNode; + +/** + * Overrides the {@link TypeInsnNode} to add a constructor + * taking in a {@link TypeProxy}. + * @since 0.4.0 + */ +public class TypeProxyInsnNode extends TypeInsnNode { + /** + * Constructs a new {@link TypeInsnNode} starting from a + * {@link TypeProxy}. The user should ensure that the TypeInsnNode + * represents a declared type before calling this. + * @param opcode the opcode, must be one of NEW, ANEWARRAY, + * CHECKCAST or INSTANCEOF + * @param t a {@link TypeProxy} representing the type to call + */ + public TypeProxyInsnNode(int opcode, TypeProxy t) { + super(opcode, t.internalName); + } +} -- cgit v1.2.3-56-ga3b1