/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered * 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 java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import com.google.common.collect.Iterators; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import org.objectweb.asm.util.CheckClassAdapter; import org.objectweb.asm.util.Printer; import org.objectweb.asm.util.TraceClassVisitor; import org.spongepowered.asm.util.asm.ASM; import org.spongepowered.asm.util.asm.MarkerNode; import org.spongepowered.asm.util.throwables.SyntheticBridgeException; import org.spongepowered.asm.util.throwables.SyntheticBridgeException.Problem; import com.google.common.base.Joiner; 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 higher or lower * than any other given visibility level. */ public enum Visibility { /** * Members decorated with {@link Opcodes#ACC_PRIVATE} */ PRIVATE(Opcodes.ACC_PRIVATE), /** * Members decorated with {@link Opcodes#ACC_PROTECTED} */ PROTECTED(Opcodes.ACC_PROTECTED), /** * Members not decorated with any access flags */ PACKAGE(0), /** * Members decorated with {@link Opcodes#ACC_PUBLIC} */ PUBLIC(Opcodes.ACC_PUBLIC); static final int MASK = Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC; final int access; private Visibility(int access) { this.access = access; } /** * Get whether this visibility level represents a level which is the * same or greater than the supplied level * * @param other level to compare to * @return true if greater or equal */ public boolean isAtLeast(Visibility other) { return other == null || other.ordinal() <= this.ordinal(); } /** * Get whether this visibility level represents a level which less than * the supplied level * * @param other level to compare to * @return true if greater or equal */ public boolean isLessThan(Visibility other) { return other != null && this.ordinal() < other.ordinal(); } } /** * Information bundle returned from {@link Bytecode#findDelegateInit} */ public static class DelegateInitialiser { /** * No delegate initialiser found */ public static final DelegateInitialiser NONE = new DelegateInitialiser(null, false); /** * Constructor invocation */ public final MethodInsnNode insn; /** * True if the invocation is a super call, false if it's a call to * another ctor in the same class */ public final boolean isSuper; /** * True if the invocation is found, false if no delegate constructor * was found (false for DelegateInitialiser.NONE) */ public final boolean isPresent; DelegateInitialiser(MethodInsnNode insn, boolean isSuper) { this.insn = insn; this.isSuper = isSuper; this.isPresent = insn != null; } @Override public String toString() { return this.isSuper ? "super" : "this"; } } /** * 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 Opcodes.CHECKCAST, // 18 Opcodes.INSTANCEOF // 19 }; private static final Object[] CONSTANTS_VALUES = { Type.VOID_TYPE, 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 = { "V", // null is returned as Type.VOID_TYPE "I", "I", "I", "I", "I", "I", "I", "J", "J", "F", "F", "F", "D", "D", "I", //"B", "I", //"S" }; /** * Mapping of {@link Type#getSort()} return values to boxing types */ private static final String[] BOXING_TYPES = { null, "java/lang/Boolean", "java/lang/Character", "java/lang/Byte", "java/lang/Short", "java/lang/Integer", "java/lang/Float", "java/lang/Long", "java/lang/Double", null, null, null }; /** * Mapping of {@link Type#getSort()} return values to boxing types */ private static final String[] UNBOXING_METHODS = { null, "booleanValue", "charValue", "byteValue", "shortValue", "intValue", "floatValue", "longValue", "doubleValue", null, null, null }; 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; } /** * Find the first insn node with a matching opcode in the specified method * * @param method method to search * @param opcode opcode to search for * @return found node or null if not found */ public static AbstractInsnNode findInsn(MethodNode method, int opcode) { Iterator findReturnIter = method.instructions.iterator(); while (findReturnIter.hasNext()) { AbstractInsnNode insn = findReturnIter.next(); if (insn.getOpcode() == opcode) { return insn; } } return null; } /** * Find the call to super() or this() in a constructor. * This attempts to locate the first call to <init> which * isn't an inline call to another object ctor being passed into the super * invocation. * * @param ctor ctor to scan * @param superName name of superclass * @param ownerName name of owning class * @return Call to super(), this() or * DelegateInitialiser.NONE if not found */ public static DelegateInitialiser findDelegateInit(MethodNode ctor, String superName, String ownerName) { if (!Constants.CTOR.equals(ctor.name)) { return DelegateInitialiser.NONE; } int news = 0; for (Iterator iter = ctor.instructions.iterator(); iter.hasNext();) { AbstractInsnNode insn = iter.next(); if (insn instanceof TypeInsnNode && insn.getOpcode() == Opcodes.NEW) { news++; } else if (insn instanceof MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESPECIAL) { MethodInsnNode methodNode = (MethodInsnNode)insn; if (Constants.CTOR.equals(methodNode.name)) { if (news > 0) { news--; } else { boolean isSuper = methodNode.owner.equals(superName); if (isSuper || methodNode.owner.equals(ownerName)) { return new DelegateInitialiser(methodNode, isSuper); } } } } } return DelegateInitialiser.NONE; } /** * 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(ClassWriter.COMPUTE_MAXS | ClassWriter.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 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 maxStack=%d maxLocals=%d\n", method.name, method.desc, method.maxStack, method.maxLocals); int index = 0; for (Iterator iter = method.instructions.iterator(); iter.hasNext();) { System.err.printf("%-4d ", index++); 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) { return Bytecode.describeNode(node, true); } /** * Gets a description of the supplied node for debugging purposes * * @param node node to describe * @param listFormat format the returned string so that returned nodes line * up (node names aligned to 14 chars) * @return human-readable description of node */ public static String describeNode(AbstractInsnNode node, boolean listFormat) { if (node == null) { return listFormat ? String.format(" %-14s ", "null") : "null"; } if (node instanceof MarkerNode) { MarkerNode marker = (MarkerNode)node; return String.format("[%s] Marker type=%d", marker.getLabel(), marker.type); } if (node instanceof LabelNode) { return String.format("[%s]", ((LabelNode)node).getLabel()); } String out = String.format(listFormat ? " %-14s " : "%s ", 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 InvokeDynamicInsnNode) { InvokeDynamicInsnNode idc = (InvokeDynamicInsnNode)node; out += String.format("[%s] %s%s { %s %s::%s%s }", Bytecode.getOpcodeName(node), idc.name, idc.desc, Bytecode.getOpcodeName(idc.bsm.getTag(), Printer.HANDLE_TAG), idc.bsm.getOwner(), idc.bsm.getName(), idc.bsm.getDesc()); } 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] ", getFrameTypeName((FrameNode) node)); } else if (node instanceof TypeInsnNode) { out += String.format("[%s] %s", Bytecode.getOpcodeName(node), ((TypeInsnNode)node).desc); } else { out += String.format("[%s] ", Bytecode.getOpcodeName(node)); } return out; } /** * Finds a constant name match for the supplied node's opcode * * @param node Node to query for opcode * @return opcode name */ public static String getOpcodeName(AbstractInsnNode node) { return node != null ? Bytecode.getOpcodeName(node.getOpcode()) : ""; } /** * Finds a constant name match for the supplied node's opcode * * @param opcode Opcode to look up * @return opcode name */ public static String getOpcodeName(int opcode) { return Bytecode.getOpcodeName(opcode, Printer.OPCODES); } private static String getOpcodeName(int opcode, String[] names) { if (opcode < 0) { return "UNKNOWN"; } if (opcode < names.length) { return names[opcode]; } return String.valueOf(opcode); } private static String getFrameTypeName(FrameNode node) { switch (node.type) { case Opcodes.F_NEW: return "F_NEW"; case Opcodes.F_FULL: return "F_FULL"; case Opcodes.F_APPEND: return "F_APPEND"; case Opcodes.F_CHOP: return "F_CHOP"; case Opcodes.F_SAME: return "F_SAME"; case Opcodes.F_SAME1: return "F_SAME1"; default: return "UNKNOWN"; } } /** * Finds a matching constant in the {@link Opcodes} * interface for the specified opcode name. Supported formats are raw * numeric values, bare constant names (eg. ACONST_NULL) or * qualified names (eg. Opcodes.ACONST_NULL). Returns the value if * found or -1 if not matched. Note that no validation is performed on * numeric opcode values. * * @param opcodeName Opcode string to match * @return matched opcode value or -1 if not matched. */ public static int parseOpcodeName(String opcodeName) { if (opcodeName == null) { return -1; } if (opcodeName.matches("^1[0-9]{0,2}|[1-9][0-9]?$")) { return Integer.parseInt(opcodeName); } if (opcodeName.startsWith("Opcodes.")) { opcodeName = opcodeName.substring(8); } if (!opcodeName.matches("^[A-Z][A-Z0-9_]+$")) { return -1; } return Bytecode.parseOpcodeName(opcodeName, Printer.OPCODES); } private static int parseOpcodeName(String name, String[] names) { for (int i = 0; i < names.length; i++) { if (name.equalsIgnoreCase(names[i])) { return i; } } return -1; } /** * Returns true if the supplied method contains any line number information * * @param method Method to scan * @return true if a line number node is located */ public static boolean methodHasLineNumbers(MethodNode method) { for (Iterator iter = method.instructions.iterator(); iter.hasNext();) { if (iter.next() instanceof LineNumberNode) { return true; } } return false; } /** * 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 isStatic(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 isStatic(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), !Bytecode.isStatic(method)); } /** * 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) { return Bytecode.getArgsSize(args, 0, args.length); } /** * Get the size of the specified args array in local variable terms (eg. * doubles and longs take two spaces) using startIndex (inclusive) and * endIndex (exclusive) to determine which arguments to process. * * @param args Method argument types as array * @param startIndex Start index in the array, not related to arg size * @param endIndex End index (exclusive) in the array, not related to size * @return size of the specified arguments array in terms of stack slots */ public static int getArgsSize(Type[] args, int startIndex, int endIndex) { int size = 0; for (int index = startIndex; index < args.length && index < endIndex; index++) { size += args[index].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 (inclusive) and ending * at end (inclusive) * * @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) { Bytecode.loadArgs(args, insns, start, end, null); } /** * Injects appropriate LOAD opcodes into the supplied InsnList appropriate * for each entry in the args array starting at start (inclusive) and ending * at end (inclusive) * * @param args Argument types * @param insns Instruction List to inject into * @param start Start position * @param end End position * @param casts Type casts array */ public static void loadArgs(Type[] args, InsnList insns, int start, int end, Type[] casts) { int pos = start; for (int index = 0; index < args.length; index++) { insns.add(new VarInsnNode(args[index].getOpcode(Opcodes.ILOAD), pos)); if (casts != null && index < casts.length && casts[index] != null) { insns.add(new TypeInsnNode(Opcodes.CHECKCAST, casts[index].getInternalName())); } pos += args[index].getSize(); if (end >= start && pos >= end) { return; } } } // /** // * Generate required APPEND frame nodes for the listed types and append them // * to the supplied instruction list // * // * @param insns Insnlist to append to // * @param types frame types to append // */ // public static void extendFrame(InsnList insns, Type[] types) { // int offset = 0; // Object[] locals = new Object[3]; // // for (int index = 0; index < types.length; index++) { // // Type type = types[index]; // int size = type.getSize(); // if (offset + size <= 3) { // if (type.getSort() < Type.ARRAY) { // locals[offset] = type.getSort(); // } else { // locals[offset] = type.getInternalName(); // } // if (size > 1) { // locals[offset + 1] = null; // } // offset += size; // } else { // insns.add(new FrameNode(Opcodes.F_APPEND, offset, locals, 0, null)); // offset = 0; // } // } // // if (offset > 0) { // insns.add(new FrameNode(Opcodes.F_APPEND, offset, locals, 0, null)); // } // } // // /** // * Generate required CHOP frame nodes for the listed types and append them // * to the supplied insn list // * // * @param insns Insnlist to append generated nodes to // * @param types types to chop // */ // public static void chopFrame(InsnList insns, Type[] types) { // int size = Bytecode.getArgsSize(types); // while (size > 0) { // int chop = Math.min(size, 3); // insns.add(new FrameNode(Opcodes.F_CHOP, chop, null, 0, null)); // size -= chop; // } // } /** * Get an array of Types from an array of classes. * * @param classes Array of classes to convert * @return Array of types */ public static Type[] getTypes(Class... classes) { Type[] types = new Type[classes.length]; for (int index = 0; index < classes.length; index++) { types[index] = Type.getType(classes[index]); } return types; } /** * 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 cloneLabels(InsnList source) { Map labels = new HashMap(); for (Iterator 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 types. * * @param returnType the method return type, can be null for * void * @param args argument types */ public static String generateDescriptor(Type returnType, Type... args) { return Bytecode.generateDescriptor(returnType, (Object[])args); } /** * 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 * null for void * @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); } return arg == null ? "" : arg.toString(); } /** * Generate a method descriptor without return type for the supplied args * array * * @param args argument types * @return method descriptor without return type */ public static String getDescriptor(Type... args) { return "(" + Joiner.on("").join(args) + ")"; } /** * Generate a method descriptor with the specified types * @param returnType return type * @param args argument types * * @return generated method descriptor */ public static String getDescriptor(Type returnType, Type... args) { return Bytecode.getDescriptor(args) + returnType.toString(); } /** * Changes the return type of a method descriptor to the specified symbol * * @param desc descriptor to modify * @param returnType new return type * @return modified descriptor; */ public static String changeDescriptorReturnType(String desc, String returnType) { if (desc == null || !desc.startsWith("(") || desc.lastIndexOf(')') < 1) { return null; } else if (returnType == null) { return desc; } return desc.substring(0, desc.lastIndexOf(')') + 1) + returnType; } /** * Returns the simple name of a type representing a class * * @param type type * @return annotation's simple name */ public static String getSimpleName(Type type) { return type.getSort() < Type.ARRAY ? type.getDescriptor() : Bytecode.getSimpleName(type.getClassName()); } /** * 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. * ICONST_1) * * @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 null if the value cannot be parsed * (null constant is returned as Type.VOID_TYPE) */ 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"); } else if (insn instanceof TypeInsnNode) { if (insn.getOpcode() < Opcodes.CHECKCAST) { return null; // Don't treat NEW and ANEWARRAY as constants } return Type.getObjectType(((TypeInsnNode)insn).desc); } 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 null if it cannot be parsed ( * null constant is returned as Type.VOID_TYPE) */ 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_DESC); } else if (cst instanceof Type) { return Type.getType(Constants.CLASS_DESC); } throw new IllegalArgumentException("LdcInsnNode with invalid payload type " + cst.getClass() + " in getConstant"); } else if (insn instanceof TypeInsnNode) { if (insn.getOpcode() < Opcodes.CHECKCAST) { return null; // Don't treat NEW and ANEWARRAY as constants } return Type.getType(Constants.CLASS_DESC); } 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); } /** * Checks whether the supplied method is virtual. Specifically this method * returns true if the method is non-static and has an access level greater * than private. * * @param method Method to test * @return true if virtual */ public static boolean isVirtual(MethodNode method) { return method != null && !Bytecode.isStatic(method) && Bytecode.getVisibility(method).isAtLeast(Visibility.PROTECTED); } /** * Returns the ordinal visibility of the supplied argument where a * higher value equals higher "visibility": * *
    *
  1. {@link Visibility#PRIVATE}
  2. *
  3. {@link Visibility#PROTECTED}
  4. *
  5. {@link Visibility#PACKAGE}
  6. *
  7. {@link Visibility#PUBLIC}
  8. *
* * @param method method to get visibility for * @return visibility level */ public static Visibility getVisibility(MethodNode method) { return Bytecode.getVisibility(method.access & 0x7); } /** * Returns the ordinal visibility of the supplied argument where a * higher value equals higher "visibility": * *
    *
  1. {@link Visibility#PRIVATE}
  2. *
  3. {@link Visibility#PROTECTED}
  4. *
  5. {@link Visibility#PACKAGE}
  6. *
  7. {@link Visibility#PUBLIC}
  8. *
* * @param field field to get visibility for * @return visibility level */ public static Visibility getVisibility(FieldNode field) { return Bytecode.getVisibility(field.access & 0x7); } /** * Returns the ordinal visibility of the supplied argument where a * higher value equals higher "visibility": * *
    *
  1. {@link Visibility#PRIVATE}
  2. *
  3. {@link Visibility#PROTECTED}
  4. *
  5. {@link Visibility#PACKAGE}
  6. *
  7. {@link Visibility#PUBLIC}
  8. *
* * @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; } /** * Set the visibility of the specified class, leaving other access flags * unchanged * * @param classNode class to change * @param visibility new visibility */ public static void setVisibility(ClassNode classNode, Visibility visibility) { classNode.access = Bytecode.setVisibility(classNode.access, visibility.access); } /** * Set the visibility of the specified member, leaving other access flags * unchanged * * @param method method to change * @param visibility new visibility */ public static void setVisibility(MethodNode method, Visibility visibility) { method.access = Bytecode.setVisibility(method.access, visibility.access); } /** * Set the visibility of the specified member, leaving other access flags * unchanged * * @param field field to change * @param visibility new visibility */ public static void setVisibility(FieldNode field, Visibility visibility) { field.access = Bytecode.setVisibility(field.access, visibility.access); } /** * Set the visibility of the specified class, leaving other access flags * unchanged * * @param classNode class to change * @param access new visibility */ public static void setVisibility(ClassNode classNode, int access) { classNode.access = Bytecode.setVisibility(classNode.access, access); } /** * Set the visibility of the specified member, leaving other access flags * unchanged * * @param method method to change * @param access new visibility */ public static void setVisibility(MethodNode method, int access) { method.access = Bytecode.setVisibility(method.access, access); } /** * Set the visibility of the specified member, leaving other access flags * unchanged * * @param field field to change * @param access new visibility */ public static void setVisibility(FieldNode field, int access) { field.access = Bytecode.setVisibility(field.access, access); } private static int setVisibility(int oldAccess, int newAccess) { return oldAccess & ~Visibility.MASK | (newAccess & Visibility.MASK); } /** * 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 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); } /** * Get the boxing type name for the specified type, if it is a primitive. * For non-primitive types, null is returned * * @param type type to box * @return boxing type or null */ public static String getBoxingType(Type type) { return type == null ? null : Bytecode.BOXING_TYPES[type.getSort()]; } /** * Get the unboxing method name for the specified primitive type's * corresponding reference type. For example, if the type passed in is * int, then the return value will be intValue. Returns * null for non-primitive types. * * @param type primitive type to get unboxing method for * @return unboxing method name or null */ public static String getUnboxingMethod(Type type) { return type == null ? null : Bytecode.UNBOXING_METHODS[type.getSort()]; } /** * Compares two synthetic bridge methods and throws an exception if they are * not compatible. * * @param a Incumbent method * @param b Incoming method */ public static void compareBridgeMethods(MethodNode a, MethodNode b) { Iterator ia = Iterators.filter(a.instructions.iterator(), Bytecode::isRealInsn); Iterator ib = Iterators.filter(b.instructions.iterator(), Bytecode::isRealInsn); int index = 0; for (; ia.hasNext() && ib.hasNext(); index++) { AbstractInsnNode na = ia.next(); AbstractInsnNode nb = ib.next(); if (na instanceof MethodInsnNode) { MethodInsnNode ma = (MethodInsnNode)na; MethodInsnNode mb = (MethodInsnNode)nb; if (!ma.name.equals(mb.name)) { throw new SyntheticBridgeException(Problem.BAD_INVOKE_NAME, a.name, a.desc, index, na, nb); } else if (!ma.desc.equals(mb.desc)) { throw new SyntheticBridgeException(Problem.BAD_INVOKE_DESC, a.name, a.desc, index, na, nb); } } else if (na.getOpcode() != nb.getOpcode()) { throw new SyntheticBridgeException(Problem.BAD_INSN, a.name, a.desc, index, na, nb); } else if (na instanceof VarInsnNode) { VarInsnNode va = (VarInsnNode)na; VarInsnNode vb = (VarInsnNode)nb; if (va.var != vb.var) { throw new SyntheticBridgeException(Problem.BAD_LOAD, a.name, a.desc, index, na, nb); } } else if (na instanceof TypeInsnNode) { TypeInsnNode ta = (TypeInsnNode)na; TypeInsnNode tb = (TypeInsnNode)nb; if (ta.getOpcode() == Opcodes.CHECKCAST && !ta.desc.equals(tb.desc)) { throw new SyntheticBridgeException(Problem.BAD_CAST, a.name, a.desc, index, na, nb); } } } if (ia.hasNext()) { throw new SyntheticBridgeException(Problem.BAD_LENGTH, a.name, a.desc, index, ia.next(), null); } if (ib.hasNext()) { throw new SyntheticBridgeException(Problem.BAD_LENGTH, a.name, a.desc, index, null, ib.next()); } } private static boolean isRealInsn(AbstractInsnNode insn) { return insn.getOpcode() != -1; } /** * Perform a naïve merge of ClassNode members onto a target ClassNode * * @param source Source ClassNode to merge from * @param dest Destination ClassNode to merge to */ public static void merge(ClassNode source, ClassNode dest) { if (source == null) { return; } if (dest == null) { throw new NullPointerException("Target ClassNode for merge must not be null"); } dest.version = Math.max(source.version, dest.version); dest.interfaces = Bytecode.merge(source.interfaces, dest.interfaces); dest.invisibleAnnotations = Bytecode.merge(source.invisibleAnnotations, dest.invisibleAnnotations); dest.visibleAnnotations = Bytecode.merge(source.visibleAnnotations, dest.visibleAnnotations); dest.visibleTypeAnnotations = Bytecode.merge(source.visibleTypeAnnotations, dest.visibleTypeAnnotations); dest.invisibleTypeAnnotations = Bytecode.merge(source.invisibleTypeAnnotations, dest.invisibleTypeAnnotations); dest.attrs = Bytecode.merge(source.attrs, dest.attrs); dest.innerClasses = Bytecode.merge(source.innerClasses, dest.innerClasses); dest.fields = Bytecode.merge(source.fields, dest.fields); dest.methods = Bytecode.merge(source.methods, dest.methods); } /** * Replace all values in a target ClassNode with values from the Source * * @param source Source ClassNode to merge from * @param dest Destination ClassNode to merge to */ public static void replace(ClassNode source, ClassNode dest) { if (source == null) { return; } if (dest == null) { throw new NullPointerException("Target ClassNode for replace must not be null"); } dest.name = source.name; dest.signature = source.signature; dest.superName = source.superName; dest.version = source.version; dest.access = source.access; dest.sourceDebug = source.sourceDebug; dest.sourceFile = source.sourceFile; dest.outerClass = source.outerClass; dest.outerMethod = source.outerMethod; dest.outerMethodDesc = source.outerMethodDesc; Bytecode.clear(dest.interfaces); Bytecode.clear(dest.visibleAnnotations); Bytecode.clear(dest.invisibleAnnotations); Bytecode.clear(dest.visibleTypeAnnotations); Bytecode.clear(dest.invisibleTypeAnnotations); Bytecode.clear(dest.attrs); Bytecode.clear(dest.innerClasses); Bytecode.clear(dest.fields); Bytecode.clear(dest.methods); if (ASM.API_VERSION >= Opcodes.ASM6) { dest.module = source.module; } // TODO Java 10 // if (MixinEnvironment.getCompatibilityLevel().supports(LanguageFeatures.NESTING)) { // ClassNodeAdapter.setNestHostClass(dest, ClassNodeAdapter.getNestHostClass(source)); // Bytecode.clear(ClassNodeAdapter.getNestMembers(dest)); // ClassNodeAdapter.setNestMembers(dest, Bytecode.merge(ClassNodeAdapter.getNestMembers(source), // ClassNodeAdapter.getNestMembers(dest))); // } Bytecode.merge(source, dest); } /** * Helper function to clear a list if it is not null */ private static void clear(List list) { if (list != null) { list.clear(); } } /** * Helper function to merge all values of a source list into a destination * taking into account that the source or destination are allowed to be null * * @param source source list * @param destination destination list * @return destination list populated with source values, new list if the * destination was null, or null if both are null or empty */ private static List merge(List source, List destination) { if (source == null || source.isEmpty()) { return destination; } if (destination == null) { return new ArrayList(source); } destination.addAll(source); return destination; } }