Java ASM 方法重写检查

标签 java bytecode java-bytecode-asm

我在方法覆盖检查方面遇到问题。我可以检测简单的覆盖关系,但是如果父类具有泛型并且抽象方法使用类型参数(返回值/args),我的代码就会崩溃,因为方法描述不等于检查的方法。

示例:

public interface ISetting<T> {

public T method();

}
public class Setting implements ISetting<Integer> {

public Integer method() {
//Something
}

}

ISetting中,方法描述为()Ljava/lang/Object;Setting中,方法描述为()Ljava/lang/Integer;

我如何检查此覆盖?

我脑子里没有任何想法,我怎么能做到这一点>~<我脑海中出现的所有想法都是不好的(例如:忽略对 desc 的检查,但重载方法只会破坏这个想法)

最佳答案

请注意,您的问题不仅适用于泛型父类(super class)型。您还可以使用更具体的返回类型重写方法,而不涉及泛型,例如

interface SomeInterface {
    Object method();
}
class SomeImplementation implements SomeInterface {
    @Override
    public Integer method() {
        return null;
    }
}

你必须理解bridge methods的概念.

桥方法执行在字节码级别重写方法的任务,具有与重写方法完全相同的参数类型和返回类型,并委托(delegate)给实际的实现方法。

由于桥方法仅由该调用指令、一些需要的类型转换和返回指令组成,因此很容易解析这样的方法以找到其所属的实际方法,而无需处理复杂的规则通用类型系统。

使用以下辅助类

record MethodSignature(String name, String desc) {}

record MethodInfo(int access, String owner, String name, String desc) {
    MethodSignature signature() {
        return new MethodSignature(name, desc);
    }
}

final class MethodAndBridges {
    MethodInfo actual;
    final List<MethodInfo> bridges = new ArrayList<>();

    MethodAndBridges(MethodSignature sig) {}

    void set(MethodInfo mi) {
        if(actual != null) throw new IllegalStateException();
        actual = mi;
    }

    void addBridge(MethodInfo mi) {
        bridges.add(mi);
    }
}

我们可以以准备检查 the ASM library 的覆盖关系的形式收集信息。如下:

class MethodCollector extends ClassVisitor {
    static Map<MethodSignature, MethodAndBridges> getMethods(ClassReader cr) {
        MethodCollector mc = new MethodCollector();
        cr.accept(mc, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
        return mc.found;
    }

    final Map<MethodSignature, MethodAndBridges> found = new HashMap<>();
    String owner, superClass;
    List<String> interfaces;

    protected MethodCollector() {
        super(Opcodes.ASM9);
    }

    @Override
    public void visit(int version, int acc,
            String name, String sig, String superName, String[] ifNames) {
        owner = name;
        superClass = superName;
        this.interfaces = ifNames == null? List.of(): List.of(ifNames);
    }

    @Override
    public MethodVisitor visitMethod(
        int acc, String name, String desc, String sig, String[] exceptions) {

        MethodInfo mi = new MethodInfo(acc, owner, name, desc);
        if((acc & Opcodes.ACC_BRIDGE) == 0) {
            found.computeIfAbsent(mi.signature(), MethodAndBridges::new).set(mi);
            return null;
        }
        return new MethodVisitor(Opcodes.ASM9) {
            @Override public void visitMethodInsn(
                    int op, String owner, String name, String tDesc, boolean i) {
                found.computeIfAbsent(new MethodSignature(name, tDesc),
                    MethodAndBridges::new).addBridge(mi);
            }
        };
    }
}

为了演示其工作原理,让我们增强您的示例,以解决更多情况

interface SupplierOfSerializable {
    Serializable get();
}

interface ISetting<T extends CharSequence> extends Supplier<T>, Consumer<T> {
    T get();
    @Override void accept(T t);
    Number method(int i);
    static void method(Object o) {}
    private void method(Number n) {}
}

class Setting implements ISetting<String>, SupplierOfSerializable {
    public String get() {
        return "";
    }
    @Override
    public void accept(String t) {}
    public Integer method(int i) {
        return i;
    }
    static void method(Object o) {}
    void method(Number n) {}
}

并检查覆盖关系(仅考虑直接接口(interface),不考虑递归)

public class CheckOverride {
    public static void main(String[] args) throws IOException {
        MethodCollector mc = new MethodCollector();
        new ClassReader(Setting.class.getName())
            .accept(mc, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

        Map<MethodSignature, MethodAndBridges> implMethods = mc.found;
        Map<MethodInfo, Set<MethodInfo>> overrides = new HashMap<>();

        for(String ifType: mc.interfaces) {
            Map<MethodSignature, MethodAndBridges> ifMethods
                = MethodCollector.getMethods(new ClassReader(ifType));

            System.out.println("interface " + ifType.replace('/', '.'));
            printMethods(ifMethods);
            System.out.println();
            ifMethods.values().removeIf(CheckOverride::nonOverridable);

            implMethods.forEach((sig, method) -> {
                if(nonOverridable(method)) {
                    overrides.putIfAbsent(method.actual, Set.of());
                    return;
                }
                var overridden = ifMethods.get(sig);
                if(overridden == null && method.bridges.isEmpty()) {
                    overrides.putIfAbsent(method.actual, Set.of());
                    return;
                }
                Set<MethodInfo> set = overrides.compute(method.actual,
                    (k, s) -> s == null || s.isEmpty()? new HashSet<>(): s);
                if(overridden != null) set.add(overridden.actual);
                for(var mi: method.bridges) {
                    overridden = ifMethods.get(mi.signature());
                    if(overridden != null) set.add(overridden.actual);
                }
            });
        }

        System.out.println("class " + mc.owner.replace('/', '.'));
        printMethods(implMethods);
        System.out.println();

        System.out.println("Final result");
        System.out.println("class " + mc.owner.replace('/', '.'));
        overrides.forEach((m,overridden) -> {
            System.out.println("  " + toDeclaration(m, false));
            if(!overridden.isEmpty()) {
                System.out.println("    overrides");
                overridden.forEach(o ->
                    System.out.println("      " + toDeclaration(o, true)));
            }
        });
    }

    static boolean nonOverridable(MethodAndBridges m) {
        return (m.actual.access() & (Opcodes.ACC_PRIVATE|Opcodes.ACC_STATIC)) != 0
             || m.actual.name().startsWith("<");
    }

    static void printMethods(Map<MethodSignature, MethodAndBridges> methods) {
        methods.forEach((sig, methodAndBridges) -> {
            System.out.println("  "+toDeclaration(methodAndBridges.actual,false));
            if(!methodAndBridges.bridges.isEmpty()) {
                System.out.println("    bridges");
                for(MethodInfo mi: methodAndBridges.bridges) {
                    System.out.println("      " + toDeclaration(mi, false));
                }
            };
        });
    }

    private static String toDeclaration(MethodInfo mi, boolean withType) {
        StringBuilder sb = new StringBuilder();
        sb.append(Modifier.toString(mi.access() & Modifier.methodModifiers()));
        if(sb.length() > 0) sb.append(' ');
        String clName = mi.owner();
        var mt = MethodTypeDesc.ofDescriptor(mi.desc());
        if(mi.name().equals("<init>"))
            sb.append(clName, clName.lastIndexOf('/') + 1, clName.length());
        else {
            sb.append(mt.returnType().displayName()).append(' ');
            if(withType) sb.append(clName.replace('/', '.')).append('.');
            sb.append(mi.name());
        }
        if(mt.parameterCount() == 0) sb.append("()");
        else {
            String sep = "(";
            for(ClassDesc cd: mt.parameterList()) {
                sb.append(sep).append(cd.displayName());
                sep = ", ";
            }
            sb.append(')');
        }
        return sb.toString();
    }
}
interface ISetting
  public static void method(Object)
  public abstract void accept(CharSequence)
    bridges
      public void accept(Object)
  public abstract Number method(int)
  private void method(Number)
  public abstract CharSequence get()
    bridges
      public Object get()

interface SupplierOfSerializable
  public abstract Serializable get()

class Setting
  Setting()
  public Integer method(int)
    bridges
      public Number method(int)
  public void accept(String)
    bridges
      public void accept(Object)
      public void accept(CharSequence)
  static void method(Object)
  public String get()
    bridges
      public Object get()
      public CharSequence get()
      public Serializable get()
  void method(Number)

Final result
class Setting
  public String get()
    overrides
      public abstract Serializable SupplierOfSerializable.get()
      public abstract CharSequence ISetting.get()
  Setting()
  public Integer method(int)
    overrides
      public abstract Number ISetting.method(int)
  public void accept(String)
    overrides
      public abstract void ISetting.accept(CharSequence)
  void method(Number)
  static void method(Object)

该代码使用了较新的 Java 功能,例如 varrecordconstant API ,但我认为,如果确实需要,结果足够简单,可以将其转换为较旧的 Java 版本。

关于Java ASM 方法重写检查,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75485622/

相关文章:

java - 是否有类似的 VisitLdcInsn 用于加载对象(不是常量)?

java - 如何在构建时在Gradle中运行控制台命令?

java - 使用 ASM (5.x) : howto? 在字节代码中检测运行时的递归方法调用

java - JVM 语言互操作性

perl - 减少 perl 启动时间的最佳方法

annotations - 字节码注入(inject)发生在哪里?

python字节码兼容性

java - 定义点击按钮

java - 二维数组初始化错误

java - 从 java 代码运行 shell 脚本并传递参数