/*
* This file is part of Mixin, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.asm.util;
import static org.spongepowered.asm.lib.ClassWriter.*;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.spongepowered.asm.lib.ClassReader;
import org.spongepowered.asm.lib.ClassWriter;
import org.spongepowered.asm.lib.MethodVisitor;
import org.spongepowered.asm.lib.Opcodes;
import org.spongepowered.asm.lib.Type;
import org.spongepowered.asm.lib.tree.*;
import org.spongepowered.asm.lib.util.CheckClassAdapter;
import org.spongepowered.asm.lib.util.TraceClassVisitor;
import com.google.common.primitives.Ints;
/**
* Utility methods for working with bytecode via ASM
*/
public final class Bytecode {
/**
* Ordinal member visibility level. This is used to represent visibility of
* a member in a formal way from lowest to highest. The
* {@link Bytecode#getVisibility} methods can be used to convert access
* flags to this enum. The value returned from {@link #ordinal} can then be
* used to determine whether a visibility level is <i>higher</i> or <i>lower
* </i> than any other given visibility level.
*/
public enum Visibility {
/**
* Members decorated with {@link Opcodes#ACC_PRIVATE}
*/
PRIVATE,
/**
* Members decorated with {@link Opcodes#ACC_PROTECTED}
*/
PROTECTED,
/**
* Members not decorated with any access flags
*/
PACKAGE,
/**
* Members decorated with {@link Opcodes#ACC_PUBLIC}
*/
PUBLIC
}
/**
* Integer constant opcodes
*/
public static final int[] CONSTANTS_INT = {
Opcodes.ICONST_M1, Opcodes.ICONST_0, Opcodes.ICONST_1, Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5
};
/**
* Float constant opcodes
*/
public static final int[] CONSTANTS_FLOAT = {
Opcodes.FCONST_0, Opcodes.FCONST_1, Opcodes.FCONST_2
};
/**
* Double constant opcodes
*/
public static final int[] CONSTANTS_DOUBLE = {
Opcodes.DCONST_0, Opcodes.DCONST_1
};
/**
* Long constant opcodes
*/
public static final int[] CONSTANTS_LONG = {
Opcodes.LCONST_0, Opcodes.LCONST_1
};
/**
* All constant opcodes
*/
public static final int[] CONSTANTS_ALL = {
Opcodes.ACONST_NULL,
Opcodes.ICONST_M1,
Opcodes.ICONST_0, Opcodes.ICONST_1, Opcodes.ICONST_2, Opcodes.ICONST_3, Opcodes.ICONST_4, Opcodes.ICONST_5,
Opcodes.LCONST_0, Opcodes.LCONST_1,
Opcodes.FCONST_0, Opcodes.FCONST_1, Opcodes.FCONST_2,
Opcodes.DCONST_0, Opcodes.DCONST_1,
Opcodes.BIPUSH, // 15
Opcodes.SIPUSH, // 16
Opcodes.LDC, // 17
};
private static final Object[] CONSTANTS_VALUES = {
null,
Integer.valueOf(-1),
Integer.valueOf(0), Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4), Integer.valueOf(5),
Long.valueOf(0L), Long.valueOf(1L),
Float.valueOf(0.0F), Float.valueOf(1.0F), Float.valueOf(2.0F),
Double.valueOf(0.0), Double.valueOf(1.0)
};
private static final String[] CONSTANTS_TYPES = {
null,
"I",
"I", "I", "I", "I", "I", "I",
"J", "J",
"F", "F", "F",
"D", "D",
"I", //"B",
"I", //"S"
};
private Bytecode() {
// utility class
}
/**
* Finds a method given the method descriptor
*
* @param classNode the class to scan
* @param name the method name
* @param desc the method descriptor
* @return discovered method node or null
*/
public static MethodNode findMethod(ClassNode classNode, String name, String desc) {
for (MethodNode method : classNode.methods) {
if (method.name.equals(name) && method.desc.equals(desc)) {
return method;
}
}
return null;
}
/**
* Runs textifier on the specified class node and dumps the output to the
* specified output stream
*
* @param classNode class to textify
* @param out output stream
*/
public static void textify(ClassNode classNode, OutputStream out) {
classNode.accept(new TraceClassVisitor(new PrintWriter(out)));
}
/**
* Runs textifier on the specified method node and dumps the output to the
* specified output stream
*
* @param methodNode method to textify
* @param out output stream
*/
public static void textify(MethodNode methodNode, OutputStream out) {
TraceClassVisitor trace = new TraceClassVisitor(new PrintWriter(out));
MethodVisitor mv = trace.visitMethod(methodNode.access, methodNode.name, methodNode.desc, methodNode.signature,
methodNode.exceptions.toArray(new String[0]));
methodNode.accept(mv);
trace.visitEnd();
}
/**
* Dumps the output of CheckClassAdapter.verify to System.out
*
* @param classNode the classNode to verify
*/
public static void dumpClass(ClassNode classNode) {
ClassWriter cw = new ClassWriter(COMPUTE_MAXS | COMPUTE_FRAMES);
classNode.accept(cw);
Bytecode.dumpClass(cw.toByteArray());
}
/**
* Dumps the output of CheckClassAdapter.verify to System.out
*
* @param bytes the bytecode of the class to check
*/
public static void dumpClass(byte[] bytes) {
ClassReader cr = new ClassReader(bytes);
CheckClassAdapter.verify(cr, true, new PrintWriter(System.out));
}
/**
* Prints a representation of a method's instructions to stderr
*
* @param method Method to print
*/
public static void printMethodWithOpcodeIndices(MethodNode method) {
System.err.printf("%s%s\n", method.name, method.desc);
int i = 0;
for (Iterator<AbstractInsnNode> iter = method.instructions.iterator(); iter.hasNext();) {
System.err.printf("[%4d] %s\n", i++, Bytecode.describeNode(iter.next()));
}
}
/**
* Prints a representation of a method's instructions to stderr
*
* @param method Method to print
*/
public static void printMethod(MethodNode method) {
System.err.printf("%s%s\n", method.name, method.desc);
for (Iterator<AbstractInsnNode> iter = method.instructions.iterator(); iter.hasNext();) {
System.err.print(" ");
Bytecode.printNode(iter.next());
}
}
/**
* Prints a representation of the specified insn node to stderr
*
* @param node Node to print
*/
public static void printNode(AbstractInsnNode node) {
System.err.printf("%s\n", Bytecode.describeNode(node));
}
/**
* Gets a description of the supplied node for debugging purposes
*
* @param node node to describe
* @return human-readable description of node
*/
public static String describeNode(AbstractInsnNode node) {
if (node == null) {
return String.format(" %-14s ", "null");
}
if (node instanceof LabelNode) {
return String.format("[%s]", ((LabelNode)node).getLabel());
}
String out = String.format(" %-14s ", node.getClass().getSimpleName().replace("Node", ""));
if (node instanceof JumpInsnNode) {
out += String.format("[%s] [%s]", Bytecode.getOpcodeName(node), ((JumpInsnNode)node).label.getLabel());
} else if (node instanceof VarInsnNode) {
out += String.format("[%s] %d", Bytecode.getOpcodeName(node), ((VarInsnNode)node).var);
} else if (node instanceof MethodInsnNode) {
MethodInsnNode mth = (MethodInsnNode)node;
out += String.format("[%s] %s %s %s", Bytecode.getOpcodeName(node), mth.owner, mth.name, mth.desc);
} else if (node instanceof FieldInsnNode) {
FieldInsnNode fld = (FieldInsnNode)node;
out += String.format("[%s] %s %s %s", Bytecode.getOpcodeName(node), fld.owner, fld.name, fld.desc);
} else if (node instanceof LineNumberNode) {
LineNumberNode ln = (LineNumberNode)node;
out += String.format("LINE=[%d] LABEL=[%s]", ln.line, ln.start.getLabel());
} else if (node instanceof LdcInsnNode) {
out += (((LdcInsnNode)node).cst);
} else if (node instanceof IntInsnNode) {
out += (((IntInsnNode)node).operand);
} else if (node instanceof FrameNode) {
out += String.format("[%s] ", Bytecode.getOpcodeName(((FrameNode)node).type, "H_INVOKEINTERFACE", -1));
} else {
out += String.format("[%s] ", Bytecode.getOpcodeName(node));
}
return out;
}
/**
* Uses reflection to find an approximate constant name match for the
* supplied node's opcode
*
* @param node Node to query for opcode
* @return Approximate opcode name (approximate because some constants in
* the {@link Opcodes} class have the same value as opcodes
*/
public static String getOpcodeName(AbstractInsnNode node) {
return Bytecode.getOpcodeName(node.getOpcode());
}
/**
* Uses reflection to find an approximate constant name match for the
* supplied opcode
*
* @param opcode Opcode to look up
* @return Approximate opcode name (approximate because some constants in
* the {@link Opcodes} class have the same value as opcodes
*/
public static String getOpcodeName(int opcode) {
return Bytecode.getOpcodeName(opcode, "UNINITIALIZED_THIS", 1);
}
private static String getOpcodeName(int opcode, String start, int min) {
if (opcode >= min) {
boolean found = false;
try {
for (java.lang.reflect.Field f : Opcodes.class.getDeclaredFields()) {
if (!found && !f.getName().equals(start)) {
continue;
}
found = true;
if (f.getType() == Integer.TYPE && f.getInt(null) == opcode) {
return f.getName();
}
}
} catch (Exception ex) {
// derp
}
}
return opcode >= 0 ? String.valueOf(opcode) : "UNKNOWN";
}
/**
* Returns true if the supplied method node is static
*
* @param method method node
* @return true if the method has the {@link Opcodes#ACC_STATIC} flag
*/
public static boolean methodIsStatic(MethodNode method) {
return (method.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
}
/**
* Returns true if the supplied field node is static
*
* @param field field node
* @return true if the field has the {@link Opcodes#ACC_STATIC} flag
*/
public static boolean fieldIsStatic(FieldNode field) {
return (field.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC;
}
/**
* Get the first variable index in the supplied method which is not an
* argument or "this" reference, this corresponds to the size of the
* arguments passed in to the method plus an extra spot for "this" if the
* method is non-static
*
* @param method MethodNode to inspect
* @return first available local index which is NOT used by a method
* argument or "this"
*/
public static int getFirstNonArgLocalIndex(MethodNode method) {
return Bytecode.getFirstNonArgLocalIndex(Type.getArgumentTypes(method.desc), (method.access & Opcodes.ACC_STATIC) == 0);
}
/**
* Get the first non-arg variable index based on the supplied arg array and
* whether to include the "this" reference, this corresponds to the size of
* the arguments passed in to the method plus an extra spot for "this" is
* specified
*
* @param args Method arguments
* @param includeThis Whether to include a slot for "this" (generally true
* for all non-static methods)
* @return first available local index which is NOT used by a method
* argument or "this"
*/
public static int getFirstNonArgLocalIndex(Type[] args, boolean includeThis) {
return Bytecode.getArgsSize(args) + (includeThis ? 1 : 0);
}
/**
* Get the size of the specified args array in local variable terms (eg.
* doubles and longs take two spaces)
*
* @param args Method argument types as array
* @return size of the specified arguments array in terms of stack slots
*/
public static int getArgsSize(Type[] args) {
int size = 0;
for (Type type : args) {
size += type.getSize();
}
return size;
}
/**
* Injects appropriate LOAD opcodes into the supplied InsnList appropriate
* for each entry in the args array starting at pos
*
* @param args Argument types
* @param insns Instruction List to inject into
* @param pos Start position
*/
public static void loadArgs(Type[] args, InsnList insns, int pos) {
Bytecode.loadArgs(args, insns, pos, -1);
}
/**
* Injects appropriate LOAD opcodes into the supplied InsnList appropriate
* for each entry in the args array starting at start and ending at end
*
* @param args Argument types
* @param insns Instruction List to inject into
* @param start Start position
* @param end End position
*/
public static void loadArgs(Type[] args, InsnList insns, int start, int end) {
int pos = start;
for (Type type : args) {
insns.add(new VarInsnNode(type.getOpcode(Opcodes.ILOAD), pos));
pos += type.getSize();
if (end >= start && pos >= end) {
return;
}
}
}
/**
* Clones all of the labels in the source instruction list and returns the
* clones in a map of old label -> new label. This is used to facilitate
* the use of {@link AbstractInsnNode#clone}.
*
* @param source instruction list
* @return map of existing labels to their cloned counterparts
*/
public static Map<LabelNode, LabelNode> cloneLabels(InsnList source) {
Map<LabelNode, LabelNode> labels = new HashMap<LabelNode, LabelNode>();
for (Iterator<AbstractInsnNode> iter = source.iterator(); iter.hasNext();) {
AbstractInsnNode insn = iter.next();
if (insn instanceof LabelNode) {
labels.put((LabelNode)insn, new LabelNode(((LabelNode)insn).getLabel()));
}
}
return labels;
}
/**
* Generate a bytecode descriptor from the supplied tokens. Each token can
* be a {@link Type}, a {@link Class} or otherwise is converted in-place by
* calling {@link Object#toString toString}.
*
* @param returnType object representing the method return type, can be
* <tt>null</tt> for <tt>void</tt>
* @param args objects representing argument types
*/
public static String generateDescriptor(Object returnType, Object... args) {
StringBuilder sb = new StringBuilder().append('(');
for (Object arg : args) {
sb.append(Bytecode.toDescriptor(arg));
}
return sb.append(')').append(returnType != null ? Bytecode.toDescriptor(returnType) : "V").toString();
}
/**
* Converts the supplied object to a descriptor component, used by
* {@link #generateDescriptor}.
*
* @param arg object to convert
*/
private static String toDescriptor(Object arg) {
if (arg instanceof String) {
return (String)arg;
} else if (arg instanceof Type) {
return arg.toString();
} else if (arg instanceof Class) {
return Type.getDescriptor((Class<?>)arg).toString();
}
return arg == null ? "" : arg.toString();
}
/**
* Returns the simple name of an annotation, mainly used for printing
* annotation names in error messages/user-facing strings
*
* @param annotationType annotation
* @return annotation's simple name
*/
public static String getSimpleName(Class<? extends Annotation> annotationType) {
return annotationType.getSimpleName();
}
/**
* Returns the simple name of an annotation, mainly used for printing
* annotation names in error messages/user-facing strings
*
* @param annotation annotation node
* @return annotation's simple name
*/
public static String getSimpleName(AnnotationNode annotation) {
return Bytecode.getSimpleName(annotation.desc);
}
/**
* Returns the simple name from an object type descriptor (in L...; format)
*
* @param desc type descriptor
* @return "simple" name
*/
public static String getSimpleName(String desc) {
int pos = Math.max(desc.lastIndexOf('/'), 0);
return desc.substring(pos + 1).replace(";", "");
}
/**
* Gets whether the supplied instruction is a constant instruction (eg.
* <tt>ICONST_1</tt>)
*
* @param insn instruction to check
* @return true if the supplied instruction is a constant
*/
public static boolean isConstant(AbstractInsnNode insn) {
if (insn == null) {
return false;
}
return Ints.contains(Bytecode.CONSTANTS_ALL, insn.getOpcode());
}
/**
* If the supplied instruction is a constant, returns the constant value
* from the instruction
*
* @param insn constant instruction to process
* @return the constant value or <tt>null</tt> if the value cannot be parsed
* (or is null)
*/
public static Object getConstant(AbstractInsnNode insn) {
if (insn == null) {
return null;
} else if (insn instanceof LdcInsnNode) {
return ((LdcInsnNode)insn).cst;
} else if (insn instanceof IntInsnNode) {
int value = ((IntInsnNode)insn).operand;
if (insn.getOpcode() == Opcodes.BIPUSH || insn.getOpcode() == Opcodes.SIPUSH) {
return Integer.valueOf(value);
}
throw new IllegalArgumentException("IntInsnNode with invalid opcode " + insn.getOpcode() + " in getConstant");
}
int index = Ints.indexOf(Bytecode.CONSTANTS_ALL, insn.getOpcode());
return index < 0 ? null : Bytecode.CONSTANTS_VALUES[index];
}
/**
* Returns the {@link Type} of a particular constant instruction's payload
*
* @param insn constant instruction
* @return type of constant or <tt>null</tt> if it cannot be parsed (or is
* null)
*/
public static Type getConstantType(AbstractInsnNode insn) {
if (insn == null) {
return null;
} else if (insn instanceof LdcInsnNode) {
Object cst = ((LdcInsnNode)insn).cst;
if (cst instanceof Integer) {
return Type.getType("I");
} else if (cst instanceof Float) {
return Type.getType("F");
} else if (cst instanceof Long) {
return Type.getType("J");
} else if (cst instanceof Double) {
return Type.getType("D");
} else if (cst instanceof String) {
return Type.getType(Constants.STRING);
} else if (cst instanceof Type) {
return Type.getType(Constants.CLASS);
}
throw new IllegalArgumentException("LdcInsnNode with invalid payload type " + cst.getClass() + " in getConstant");
}
int index = Ints.indexOf(Bytecode.CONSTANTS_ALL, insn.getOpcode());
return index < 0 ? null : Type.getType(Bytecode.CONSTANTS_TYPES[index]);
}
/**
* Check whether the specified flag is set on the specified class
*
* @param classNode class node
* @param flag flag to check
* @return True if the specified flag is set in this method's access flags
*/
public static boolean hasFlag(ClassNode classNode, int flag) {
return (classNode.access & flag) == flag;
}
/**
* Check whether the specified flag is set on the specified method
*
* @param method method node
* @param flag flag to check
* @return True if the specified flag is set in this method's access flags
*/
public static boolean hasFlag(MethodNode method, int flag) {
return (method.access & flag) == flag;
}
/**
* Check whether the specified flag is set on the specified field
*
* @param field field node
* @param flag flag to check
* @return True if the specified flag is set in this field's access flags
*/
public static boolean hasFlag(FieldNode field, int flag) {
return (field.access & flag) == flag;
}
/**
* Check whether the status of the specified flag matches on both of the
* supplied arguments.
*
* @param m1 First method
* @param m2 Second method
* @param flag flag to compare
* @return True if the flag is set to the same value on both members
*/
public static boolean compareFlags(MethodNode m1, MethodNode m2, int flag) {
return Bytecode.hasFlag(m1, flag) == Bytecode.hasFlag(m2, flag);
}
/**
* Check whether the status of the specified flag matches on both of the
* supplied arguments.
*
* @param f1 First field
* @param f2 Second field
* @param flag flag to compare
* @return True if the flag is set to the same value on both members
*/
public static boolean compareFlags(FieldNode f1, FieldNode f2, int flag) {
return Bytecode.hasFlag(f1, flag) == Bytecode.hasFlag(f2, flag);
}
/**
* Returns the <i>ordinal visibility</i> of the supplied argument where a
* higher value equals higher "visibility":
*
* <ol start="0">
* <li>{@link #Visibility.PRIVATE}</li>
* <li>{@link #Visibility.PROTECTED}</li>
* <li>{@link #Visibility.PACKAGE}</li>
* <li>{@link #Visibility.PUBLIC}</li>
* </ol>
*
* @param method method to get visibility for
* @return visibility level
*/
public static Visibility getVisibility(MethodNode method) {
return Bytecode.getVisibility(method.access & 0x7);
}
/**
* Returns the <i>ordinal visibility</i> of the supplied argument where a
* higher value equals higher "visibility":
*
* <ol start="0">
* <li>{@link Visibility#PRIVATE}</li>
* <li>{@link Visibility#PROTECTED}</li>
* <li>{@link Visibility#PACKAGE}</li>
* <li>{@link Visibility#PUBLIC}</li>
* </ol>
*
* @param field field to get visibility for
* @return visibility level
*/
public static Visibility getVisibility(FieldNode field) {
return Bytecode.getVisibility(field.access & 0x7);
}
/**
* Returns the <i>ordinal visibility</i> of the supplied argument where a
* higher value equals higher "visibility":
*
* <ol start="0">
* <li>{@link Visibility#PRIVATE}</li>
* <li>{@link Visibility#PROTECTED}</li>
* <li>{@link Visibility#PACKAGE}</li>
* <li>{@link Visibility#PUBLIC}</li>
* </ol>
*
* @param flags access flags of member
* @return visibility level
*/
private static Visibility getVisibility(int flags) {
if ((flags & Opcodes.ACC_PROTECTED) != 0) {
return Bytecode.Visibility.PROTECTED;
} else if ((flags & Opcodes.ACC_PRIVATE) != 0) {
return Bytecode.Visibility.PRIVATE;
} else if ((flags & Opcodes.ACC_PUBLIC) != 0) {
return Bytecode.Visibility.PUBLIC;
}
return Bytecode.Visibility.PACKAGE;
}
/**
* Compute the largest line number found in the specified class
*
* @param classNode Class to inspect
* @param min minimum value to return
* @param pad amount to pad at the end of files
* @return computed max
*/
public static int getMaxLineNumber(ClassNode classNode, int min, int pad) {
int max = 0;
for (MethodNode method : classNode.methods) {
for (Iterator<AbstractInsnNode> iter = method.instructions.iterator(); iter.hasNext();) {
AbstractInsnNode insn = iter.next();
if (insn instanceof LineNumberNode) {
max = Math.max(max, ((LineNumberNode)insn).line);
}
}
}
return Math.max(min, max + pad);
}
}