package de.take_weiland.mods.commons.asm; import com.google.common.collect.FluentIterable; import de.take_weiland.mods.commons.asm.info.ClassInfo; import net.minecraft.launchwrapper.IClassNameTransformer; import net.minecraft.launchwrapper.Launch; import org.objectweb.asm.ClassReader; import org.objectweb.asm.Type; import org.objectweb.asm.commons.GeneratorAdapter; import org.objectweb.asm.tree.ClassNode; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; /** * <p>A collection of utility methods for working with the ASM library.</p> */ @ParametersAreNonnullByDefault public final class ASMUtils { private ASMUtils() { } public static void convertTypes(GeneratorAdapter gen, Class<?> from, Class<?> to) { convertTypes(gen, Type.getType(from), Type.getType(to)); } /** * <p>Convert from one type to another.</p> * <p>The following conversions are allowed:</p> * <ul> * <p>primitive -> primitive</p> * <p>primitive -> wrapper (only direct conversion from a primitive to it's wrapper)</p> * <p>wrapper -> primitive (again only direct conversion)</p> * <p>class -> class (may be a no-op or a checkcast)</p> * </ul> * @param gen the generator adapter * @param from the type on top of the stack * @param to the type to convert to */ public static void convertTypes(GeneratorAdapter gen, Type from, Type to) { if (isPrimitive(from) && isPrimitive(to)) { gen.cast(from, to); } else { if (isPrimitive(from)) { Type boxed = boxedType(from); box(gen, boxed); if (!isAssignableFrom(to, boxed)) { gen.checkCast(to); } } else if (isPrimitive(to)) { Type boxed = boxedType(to); if (!isAssignableFrom(to, from)) { gen.checkCast(boxed); } unbox(gen, to); } else { if (!isAssignableFrom(to, from)) { gen.checkCast(to); } } } } private static boolean isAssignableFrom(Type a, Type b) { return ClassInfo.of(a).isAssignableFrom(ClassInfo.of(b)); } private static void box(GeneratorAdapter gen, Type boxed) { Type primT = unboxedType(boxed); org.objectweb.asm.commons.Method boxMethod = new org.objectweb.asm.commons.Method("valueOf", boxed, new Type[] { primT }); gen.invokeStatic(boxed, boxMethod); } private static void unbox(GeneratorAdapter gen, Type primitive) { Type boxedT = boxedType(primitive); org.objectweb.asm.commons.Method unboxMethod = new org.objectweb.asm.commons.Method(primitive.getClassName() + "Value", primitive, new Type[0]); gen.invokeVirtual(boxedT, unboxMethod); } // *** name utilities *** // /** * <p>Convert the given binary name (e.g. {@code java.lang.Object$Subclass}) to an internal name (e.g. {@code java/lang/Object$Subclass}).</p> * * @param binaryName the binary name * @return the internal name */ public static String internalName(String binaryName) { return binaryName.replace('.', '/'); } /** * <p>Convert the given internal name to a binary name (opposite of {@link #internalName(String)}).</p> * * @param internalName the internal name * @return the binary name */ public static String binaryName(String internalName) { return internalName.replace('/', '.'); } private static IClassNameTransformer nameTransformer; private static boolean nameTransChecked = false; /** * <p>Get the active {@link net.minecraft.launchwrapper.IClassNameTransformer}, if any.</p> * * @return the active transformer, or null if none */ @Nullable public static IClassNameTransformer getClassNameTransformer() { if (!nameTransChecked) { nameTransformer = FluentIterable.from(Launch.classLoader.getTransformers()) .filter(IClassNameTransformer.class) .first().orNull(); nameTransChecked = true; } return nameTransformer; } /** * <p>Transform the class name with the current {@link net.minecraft.launchwrapper.IClassNameTransformer}, if any. Returns the untransformed * name if no transformer is present.</p> * * @param untransformedName the un-transformed internal name of the class * @return the transformed internal name of the class */ public static String transformName(String untransformedName) { IClassNameTransformer t = getClassNameTransformer(); return internalName(t == null ? untransformedName : t.remapClassName(binaryName(untransformedName))); } /** * <p>Un-transform the class name with the current {@link net.minecraft.launchwrapper.IClassNameTransformer}, if any. Returns * the transformed name if no transformer is present.</p> * * @param transformedName the transformed internal name of the class * @return the un-transformed internal name of the class */ public static String untransformName(String transformedName) { IClassNameTransformer t = getClassNameTransformer(); return internalName(t == null ? transformedName : t.unmapClassName(binaryName(transformedName))); } // *** Misc Utils *** // /** * <p>Create a ClassNode for the class represented by the given bytes.</p> * @param bytes the class bytes * @return a ClassNode */ public static ClassNode getClassNode(byte[] bytes) { return getClassNode(bytes, 0); } /** * <p>Create a ClassNode for the class represented by the given bytes.</p> * * @param bytes the class bytes * @param readerFlags the flags to pass to the {@link org.objectweb.asm.ClassReader} * @return a ClassNode */ public static ClassNode getClassNode(byte[] bytes, int readerFlags) { ClassReader reader = new ClassReader(bytes); ClassNode clazz = new ClassNode(); reader.accept(clazz, readerFlags); return clazz; } private static final int THIN_FLAGS = ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES; /** * <p>Create a ClassNode for the class represented by the given bytes.</p> * <p>The ClassNode will only contain the rough outline of the class. It is read with {@code ClassReader.SKIP_CODE}, {@code ClassReader.SKIP_DEBUG} * {@code ClassReader.SKIP_FRAMES} set.</p> * @param bytes the class bytes * @return a ClassNode * */ public static ClassNode getThinClassNode(byte[] bytes) { return getClassNode(bytes, THIN_FLAGS); } // *** annotation utilities *** // /** * <p>Checks if the given {@link org.objectweb.asm.Type} represents a primitive type or the void type.</p> * * @param type the type * @return true if the {@code Type} represents a primitive type or void */ public static boolean isPrimitive(Type type) { return type.getSort() != Type.ARRAY && type.getSort() != Type.OBJECT && type.getSort() != Type.METHOD; } /** * <p>Checks if the given {@code Type} represents a primitive wrapper such as {@code Integer} or the {@code Void} type.</p> * @param type * @return */ public static boolean isPrimitiveWrapper(Type type) { return unboxedType(type) != type; } /** * <p>Get a Type representing the unboxed version of the given primitive wrapper type. If the given type is not * a primitive wrapper, the type itself will be returned.</p> * @param wrapper the wrapper * @return the unboxed version */ public static Type unboxedType(Type wrapper) { if (isPrimitive(wrapper)) { return wrapper; } switch (wrapper.getInternalName()) { case "java/lang/Void": return Type.VOID_TYPE; case "java/lang/Boolean": return Type.BOOLEAN_TYPE; case "java/lang/Byte": return Type.BYTE_TYPE; case "java/lang/Short": return Type.SHORT_TYPE; case "java/lang/Character": return Type.CHAR_TYPE; case "java/lang/Integer": return Type.INT_TYPE; case "java/lang/Long": return Type.LONG_TYPE; case "java/lang/Float": return Type.FLOAT_TYPE; case "java/lang/Double": return Type.DOUBLE_TYPE; default: return wrapper; } } /** * <p>Get a Type representing the boxed version of the given primitive type. If the given type is not * a primitive, the type itself will be returned.</p> * @param primitive the primitive * @return the boxed version */ public static Type boxedType(Type primitive) { switch (primitive.getSort()) { case Type.VOID: return Type.getType(Void.class); case Type.BOOLEAN: return Type.getType(Boolean.class); case Type.BYTE: return Type.getType(Byte.class); case Type.SHORT: return Type.getType(Short.class); case Type.CHAR: return Type.getType(Character.class); case Type.INT: return Type.getType(Integer.class); case Type.LONG: return Type.getType(Long.class); case Type.FLOAT: return Type.getType(Float.class); case Type.DOUBLE: return Type.getType(Double.class); default: return primitive; } } }