如何在修改版ASM中定位触发‘Method code too large’的方法?
我之前也碰到过类似的场景——用定制版ASM没法直接用官方解决方案,还得先定位出哪个方法字节码超标的问题。给你几个不用大幅修改现有ASM包、成本较低的方案:
方案1:提前用ClassReader做预检测(最推荐)
不用等到ClassWriter报错,我们可以先通过ClassReader遍历类的字节码,提前统计每个方法的字节码长度,直接找出超标的方法。
核心思路是实现一个自定义的ClassVisitor和MethodVisitor,在遍历方法指令时累加字节码的长度(ASM的每个visitXxx方法对应固定长度的字节码指令,这个可以参考ASM源码里的指令长度定义),当长度超过65535(JVM规定的单个方法字节码最大长度)时,直接记录方法信息。
示例代码大概是这样:
public class SizeCheckingClassVisitor extends ClassVisitor { private String currentClassName; public SizeCheckingClassVisitor() { super(Opcodes.ASM9); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { this.currentClassName = name; super.visit(version, access, name, signature, superName, interfaces); } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); return new SizeCheckingMethodVisitor(mv, currentClassName, name, descriptor); } private static class SizeCheckingMethodVisitor extends MethodVisitor { private final String className; private final String methodName; private final String methodDesc; private int codeSize = 0; public SizeCheckingMethodVisitor(MethodVisitor mv, String className, String methodName, String methodDesc) { super(Opcodes.ASM9, mv); this.className = className; this.methodName = methodName; this.methodDesc = methodDesc; } // 重写所有visitXxx方法,累加对应指令的字节码长度 @Override public void visitInsn(int opcode) { codeSize += 1; // 大多数单字节指令 checkSize(); super.visitInsn(opcode); } @Override public void visitVarInsn(int opcode, int var) { codeSize += (var < 4) ? 2 : 3; // 短变量索引是2字节,长索引是3字节 checkSize(); super.visitVarInsn(opcode, var); } @Override public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { codeSize += 3; checkSize(); super.visitFieldInsn(opcode, owner, name, descriptor); } // ... 还要重写其他visit方法,比如visitLdcInsn、visitMethodInsn等,对应不同的指令长度 // 可以参考ASM源码里MethodWriter中记录指令长度的逻辑 private void checkSize() { if (codeSize > 65535) { System.err.printf("Warning: Method %s.%s%s in class %s exceeds maximum code size (%d > 65535)%n", className, methodName, methodDesc, className, codeSize); } } } }
使用时只需要在处理类前先跑一遍这个检测:
ClassReader cr = new ClassReader(classBytes); cr.accept(new SizeCheckingClassVisitor(), 0);
这个方案完全独立于你正在使用的修改版ClassWriter,不需要碰任何定制代码,就能提前定位问题方法。
方案2:给定制版ASM的MethodWriter加个小补丁
如果允许对定制版ASM的源码做极小改动,可以直接在触发报错的MethodWriter.a()方法里加个检测逻辑——每次添加指令后检查当前字节码长度,一旦超过阈值就打印方法信息。
比如找到MethodWriter类里维护字节码长度的变量(通常是类似codeLength的字段),在每次修改这个变量后加一段判断:
// 在MethodWriter的指令添加逻辑里,比如a()方法中 codeLength += ...; // 原有累加逻辑 if (codeLength > 65535) { System.err.printf("Method too large: %s.%s%s in class %s%n", className, methodName, methodDesc, className); }
这样当toByteArray()抛出异常前,你已经能看到具体是哪个方法超标的信息了,改动量非常小,不需要复制整个包。
方案3:用TraceClassVisitor辅助定位
如果不想写太多代码,也可以用ASM自带的TraceClassVisitor配合日志输出。把它和你的定制版ClassWriter组合起来,这样在处理每个方法时会打印方法的定义信息,当toByteArray()报错时,最后输出的那个方法就是问题方法。
示例代码:
ClassWriter cw = new YourCustomClassWriter(ClassWriter.COMPUTE_FRAMES); TraceClassVisitor traceVisitor = new TraceClassVisitor(cw, new PrintWriter(System.err)); // 用traceVisitor来处理类的字节码,而不是直接用cw
这样处理过程中,每个方法开始时会打印类似public void test() {的日志,报错前的最后一条方法日志就是你要找的目标。
内容的提问来源于stack exchange,提问作者User1291




