package tc.oc.javassist; import java.util.Objects; import javax.annotation.Nullable; import javassist.CannotCompileException; import javassist.ClassPool; import javassist.CtBehavior; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtMethod; import javassist.CtNewConstructor; import javassist.CtNewMethod; import javassist.Modifier; import javassist.NotFoundException; import javassist.bytecode.AnnotationsAttribute; import javassist.bytecode.AttributeInfo; import javassist.bytecode.Bytecode; import javassist.bytecode.ConstPool; import javassist.bytecode.MethodInfo; import javassist.bytecode.ParameterAnnotationsAttribute; import javassist.scopedpool.ScopedClassPoolRepositoryImpl; import static com.google.common.base.Preconditions.checkArgument; /** * Helpers for use with the Javassist bytecode generation library */ public class Javassists { /** * Return a {@link ClassPool} scoped to the given {@link ClassLoader}. * * This is essential in a Bukkit/Bungee environment. The default * ClassPool is practically useless, since it does not have access to * the private plugin ClassLoaders. */ public static ClassPool getPool(ClassLoader loader) { return ScopedClassPoolRepositoryImpl.getInstance().registerClassLoader(loader); } public static CtClass getClass(Class<?> cls) throws NotFoundException { return getPool(cls.getClassLoader()).get(cls.getName()); } public static AttributeInfo inPool(AttributeInfo attr, ConstPool pool) { return pool.equals(attr.getConstPool()) ? attr : attr.copy(pool, null); } public static CtMethod getMethod(CtClass decl, CtMethod method) throws NotFoundException { return decl.getMethod(method.getName(), method.getSignature()); } public static @Nullable CtMethod tryMethod(CtClass decl, CtMethod method) { try { return getMethod(decl, method); } catch(NotFoundException e) { return null; } } public static AttributeInfo addAttribute(MethodInfo to, AttributeInfo attr) { attr = inPool(attr, to.getConstPool()); to.addAttribute(attr); return attr; } public static @Nullable AttributeInfo copyAttribute(MethodInfo from, MethodInfo to, String attributeName) { final AttributeInfo attr = from.getAttribute(attributeName); if(attr != null) { addAttribute(to, attr); } return attr; } public static void copyAnnotations(MethodInfo from, MethodInfo to) { copyAttribute(from, to, AnnotationsAttribute.invisibleTag); copyAttribute(from, to, AnnotationsAttribute.visibleTag); copyAttribute(from, to, ParameterAnnotationsAttribute.invisibleTag); copyAttribute(from, to, ParameterAnnotationsAttribute.visibleTag); } public static boolean isSubtype(CtClass base, CtClass sub) throws NotFoundException { if(sub == null) return false; if(base.equals(sub)) return true; if(isSubtype(base, sub.getSuperclass())) return true; for(CtClass iface : sub.getInterfaces()) { if(isSubtype(base, iface)) return true; } return false; } public static boolean isPrivate(CtBehavior method) { return Modifier.isPrivate(method.getModifiers()); } public static boolean isAbstract(CtBehavior method) { return Modifier.isAbstract(method.getModifiers()); } public static boolean isFinal(CtBehavior method) { return Modifier.isFinal(method.getModifiers()); } public static void setModifier(CtBehavior method, int mask, boolean value) { method.setModifiers(value ? method.getModifiers() | mask : method.getModifiers() & ~mask); } public static final int ACCESS_MODIFIERS = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; public static int getAccessModifiers(int modifiers) { return modifiers & ACCESS_MODIFIERS; } public static void setAccessModifiers(CtClass cls, int modifiers) { cls.setModifiers((cls.getModifiers() & ~ACCESS_MODIFIERS) | (modifiers & ACCESS_MODIFIERS)); } public static void setAccessModifiers(CtBehavior method, int modifiers) { method.setModifiers((method.getModifiers() & ~ACCESS_MODIFIERS) | (modifiers & ACCESS_MODIFIERS)); } public static boolean isAccessible(CtBehavior accessible, CtClass from) throws NotFoundException { return isAccessible(accessible.getDeclaringClass(), accessible.getModifiers(), from); } public static boolean isAccessible(CtClass accessible, CtClass from) throws NotFoundException { return isAccessible(accessible, accessible.getModifiers(), from); } /** * TODO: This doesn't handle every case yet */ public static boolean isAccessible(CtClass accessible, int modifiers, CtClass from) throws NotFoundException { final CtClass decl = accessible.getDeclaringClass(); if(decl != null && !isAccessible(decl, from)) return false; if(Modifier.isPrivate(modifiers)) { return from.equals(accessible); } else if(Modifier.isProtected(modifiers)) { return isSubtype(accessible, from); } else if(Modifier.isPublic(modifiers)) { return true; } else { return Objects.equals(accessible.getPackageName(), from.getPackageName()); } } public static CtConstructor inheritConstructor(CtClass subclass, CtConstructor constructor) throws NotFoundException, CannotCompileException { checkArgument(Objects.equals(subclass.getSuperclass(), constructor.getDeclaringClass()), "Constructor " + constructor.getLongName() + " is not declared in " + subclass.getSuperclass().getName() + ", the superclass of " + subclass.getName()); checkArgument(isAccessible(constructor, subclass), "Constructor " + constructor.getLongName() + " is not accessible from " + subclass.getName()); final CtConstructor delegator = CtNewConstructor.make(constructor.getParameterTypes(), constructor.getExceptionTypes(), CtNewConstructor.PASS_PARAMS, null, null, subclass); // Must use getMethodInfo2 on the original ctor because the class is frozen copyAnnotations(constructor.getMethodInfo2(), delegator.getMethodInfo()); subclass.addConstructor(delegator); return delegator; } public static void inheritAllConstructors(CtClass subclass) throws NotFoundException, CannotCompileException { final CtClass superclass = subclass.getSuperclass(); if(superclass != null) { for(CtConstructor ctor : superclass.getConstructors()) { if(!isPrivate(ctor)) { inheritConstructor(subclass, ctor); } } } } public static CtMethod delegateMethod(CtClass implClass, CtMethod method, CtMethod delegateGetter) throws NotFoundException, CannotCompileException { checkArgument(!Modifier.isStatic(method.getModifiers()), "Cannot delegate static method " + method.getLongName()); checkArgument(!Modifier.isStatic(delegateGetter.getModifiers()), "Delegate getter method " + delegateGetter.getLongName() + " must not be static"); checkArgument(isSubtype(delegateGetter.getDeclaringClass(), implClass), "Implementation class " + implClass + " does not contain delegate getter method " + delegateGetter.getLongName()); checkArgument(delegateGetter.getParameterTypes().length == 0, "Delegate getter method " + delegateGetter.getLongName() + " must not take any parameters"); checkArgument(!delegateGetter.getReturnType().isPrimitive(), "Delegate getter method " + delegateGetter.getLongName() + " must return an object"); try { final CtMethod conflict = implClass.getDeclaredMethod(method.getName(), method.getParameterTypes()); throw new IllegalArgumentException("Class " + implClass.getName() + " already contains a method " + conflict.getLongName() + " that conflicts with the delegated method " + method.getLongName()); } catch(NotFoundException ignored) {} final String name = method.getName(); final CtClass[] paramTypes = method.getParameterTypes(); final CtClass returnType = method.getReturnType(); final MethodInfo info = method.getMethodInfo2(); // Must be read-only final String descriptor = info.getDescriptor(); final CtClass decl = method.getDeclaringClass(); final Bytecode bc = new Bytecode(implClass.getClassFile().getConstPool()); int maxStack = 1; bc.addAload(0); // push this bc.addInvokeinterface(delegateGetter.getDeclaringClass(), delegateGetter.getName(), delegateGetter.getReturnType(), null, 1); // get delegate bc.addCheckcast(decl); // make the verifier happy maxStack += bc.addLoadParameters(paramTypes, 1); // push all params if(decl.isInterface()) { // call the method on delegate bc.addInvokeinterface(decl, name, descriptor, 1 + paramTypes.length); } else { bc.addInvokevirtual(decl, name, descriptor); } bc.addReturn(returnType); // forward the return value bc.setMaxLocals(false, paramTypes, 0); bc.setMaxStack(Math.max(2, maxStack)); // return value might be up to 2 stack ops final CtMethod delegator = CtNewMethod.copy(method, implClass, null); setAccessModifiers(delegator, method.getModifiers()); setModifier(delegator, Modifier.ABSTRACT, false); setModifier(delegator, Modifier.NATIVE, false); delegator.getMethodInfo().setCodeAttribute(bc.toCodeAttribute()); implClass.addMethod(delegator); return delegator; } }