/******************************************************************************************************************* * Authors: SanAndreasP * Copyright: SanAndreasP * License: Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International * http://creativecommons.org/licenses/by-nc-sa/4.0/ *******************************************************************************************************************/ package de.sanandrew.core.manpack.transformer; import de.sanandrew.core.manpack.init.ManPackLoadingPlugin; import net.minecraft.launchwrapper.IClassTransformer; import org.apache.logging.log4j.Level; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; import java.util.regex.Matcher; public class AnnotationChecker implements IClassTransformer { @Override public byte[] transform(String name, String transformedName, byte[] bytes) { ClassNode cn = ASMHelper.createClassNode(bytes); for( MethodNode method : cn.methods ) { if( method.visibleAnnotations != null ) { for( AnnotationNode annotation : method.visibleAnnotations ) { if( annotation.desc.equals("Lde/sanandrew/core/manpack/util/annotation/ASMOverride;") ) { String asmMethodName = annotation.values.get(1).toString(); MethodNode asmMethod = getSignature(asmMethodName); if( !method.name.equals(asmMethod.name) ) { String err = "Attempting to override Method %s in Class %s with incompatible name %s!"; ManPackLoadingPlugin.MOD_LOG.log(Level.FATAL, String.format(err, asmMethod.name, cn.name, method.name)); throw new OverrideException(String.format("Method name %s is not equal to %s!", method.name, asmMethod.name)); } else if( !method.desc.equals(asmMethod.desc) ) { String err = "Attempting to override Method %s, description %s, in Class %s with incompatible description %s!"; ManPackLoadingPlugin.MOD_LOG.log(Level.FATAL, String.format(err, asmMethod.name, asmMethod.desc, cn.name, method.desc)); throw new OverrideException(String.format("Method desc %s is not equal to %s!", method.desc, asmMethod.desc)); } else if( getAccessLevelInt(method.access) < getAccessLevelInt(asmMethod.access) ) { String err = "Attempting to assign weaker access privileges ('%s') on %s in %s; should be '%s'"; ManPackLoadingPlugin.MOD_LOG.log(Level.FATAL, String.format(err, getAccessLevelName(method.access), asmMethod.name, cn.name, getAccessLevelName(asmMethod.access))); throw new OverrideException(String.format("Access level %s is weaker than %s!", getAccessLevelName(method.access), getAccessLevelName(asmMethod.access))); } else if( getAccessLevelInt(asmMethod.access) == 1 ) { String classPkg = cn.name.substring(0, cn.name.lastIndexOf('/')); String ownerPkg = ASMHelper.getMethodInsnNode(asmMethod.access, asmMethodName, false).owner.substring(0, cn.name.lastIndexOf('/')); if( !classPkg.equals(ownerPkg) ) { String err = "Attempting to override packageLocal method %s outside of package %s in class %s, which is in package %s!"; ManPackLoadingPlugin.MOD_LOG.log(Level.FATAL, String.format(err, method.name, ownerPkg, cn.name, classPkg)); throw new OverrideException(String.format("Class %s lies outside package %s for method %s to be overridden", cn.name, ownerPkg, classPkg)); } } else if( checkBitwiseEqual(asmMethod.access, Opcodes.ACC_DEPRECATED) ) { ManPackLoadingPlugin.MOD_LOG.log(Level.WARN, String.format("The Method %s is marked as deprecated! It is most likely that the method is " + "not injected in any superclass anymore! Thus this may not be called!", asmMethod.name)); } } } } } return bytes; } private static MethodNode getSignature(String method) { String split[] = method.split(" "); int accLvl = getAccessLevelOpcode(split[0]); Matcher mtch = ASMNames.OWNERNAME.matcher(split[1]); if( !mtch.find() ) { throw new RuntimeException("SAP-Method signature invalid!"); } String name = mtch.group(2); String desc = split[2]; String sig = split.length > 3 ? split[3] : null; String throwing[] = split.length > 4 ? split[4].split(";") : null; return new MethodNode(accLvl, name, desc, sig, throwing); } private static int getAccessLevelInt(int access) { if( checkBitwiseEqual(access, Opcodes.ACC_PRIVATE) ) { return 0; } else if( checkBitwiseEqual(access, Opcodes.ACC_PROTECTED) ) { return 2; } else if( checkBitwiseEqual(access, Opcodes.ACC_PUBLIC) ) { return 3; } else { // if ACC_PACKAGE_LOCAL return 1; } } private static String getAccessLevelName(int access) { if( checkBitwiseEqual(access, Opcodes.ACC_PRIVATE) ) { return "private"; } else if( checkBitwiseEqual(access, Opcodes.ACC_PROTECTED) ) { return "protected"; } else if( checkBitwiseEqual(access, Opcodes.ACC_PUBLIC) ) { return "public"; } else { // if ACC_PACKAGE_LOCAL return "packageLocal"; } } private static int getAccessLevelOpcode(String accessName) { String[] accessSplit = accessName.split(";"); int ret = 0; for( String acc : accessSplit ) { switch( acc ) { case "private": ret |= Opcodes.ACC_PRIVATE; break; case "protected": ret |= Opcodes.ACC_PROTECTED; break; case "public": ret |= Opcodes.ACC_PUBLIC; break; case "deprecated": ret |= Opcodes.ACC_DEPRECATED; break; } } return ret; } private static boolean checkBitwiseEqual(int value, int flag) { return (value & flag) == flag; } public static class OverrideException extends RuntimeException { private static final long serialVersionUID = 7395488467026629355L; public OverrideException(String message) { super(message); } } }