You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用BCEL与bcel-util生成通过字节码验证的Java类?

解决BCEL生成含switch语句类的栈映射帧验证错误

问题背景

你尝试用BCEL生成一个带switch语句的HelloWorld类,目标代码如下:

public class HelloWorld {

    public static void main(String[] args) {
        switch (args[0]) {
        case "Hello":
            System.out.println("Hello World");
            break;
        default:
            break;
        }
    }
}

使用的Build配置:

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.apache.bcel:bcel:6.6.1'
    implementation 'org.plumelib:bcel-util:1.1.16'
}

用BCELifier生成HelloWorldCreator类后,运行生成的HelloWorld.class时出现以下验证错误:

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.VerifyError: Expecting a stackmap frame at branch target 51
Exception Details:
  Location:
    HelloWorld.main([Ljava/lang/String;)V @8: lookupswitch
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: 2a03 3259 4cb6 0014 ab00 0000 0000 002b
    0x0000010: 0000 0001 0426 28b2 0000 0014 2b12 16b6
    0x0000020: 001a 9a00 06a7 000e b200 2012 22b6 0028
    0x0000030: a700 03b1

        at java.lang.Class.getDeclaredMethods0(Native Method)
        at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
        at java.lang.Class.privateGetMethodRecursive(Class.java:3048)
        at java.lang.Class.getMethod0(Class.java:3018)
        at java.lang.Class.getMethod(Class.java:1784)
        at sun.launcher.LauncherHelper.validateMainClass(LauncherHelper.java:650)
        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:632)

即便引入bcel-util并添加栈映射处理逻辑,问题依然存在。

原因分析

问题出在两个核心点:

  1. 生成方法时调用了il.dispose();,释放了指令列表资源,导致后续修改类时无法获取有效指令列表,无法生成正确的栈映射帧。
  2. 栈映射帧处理时机错误,先将方法添加到ClassGen再事后修改,此时指令资源已被释放,处理逻辑无法生效。

修正后的代码

以下是调整后的HelloWorldCreator代码,修复了上述问题:

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.generic.ArrayType;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.InstructionConst;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LOOKUPSWITCH;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.PUSH;
import org.apache.bcel.generic.Select;
import org.apache.bcel.generic.Type;
import org.plumelib.bcelutil.InstructionListUtils;

public class HelloWorldCreator extends InstructionListUtils {
    private InstructionFactory _factory;
    private ConstantPoolGen _cp;
    private ClassGen _cg;

    public HelloWorldCreator() {
        _cg = new ClassGen("HelloWorld", "java.lang.Object", "HelloWorld.java", Const.ACC_PUBLIC | Const.ACC_SUPER,
                new String[] {});
        _cg.setMajor(52);
        _cg.setMinor(0);

        _cp = _cg.getConstantPool();
        _factory = new InstructionFactory(_cg, _cp);
    }

    private void processMethod(MethodGen mg) {
        InstructionList il = mg.getInstructionList();
        if (il == null) {
            return;
        }

        // 初始化栈映射相关资源
        pool = _cp;
        set_current_stack_map_table(mg, _cg.getMajor());
        fix_local_variable_table(mg);

        build_unitialized_NEW_map(il);
        update_uninitialized_NEW_offsets(il);
        create_new_stack_map_attribute(mg);

        // 更新方法的最大栈和本地变量数
        mg.setMaxStack();
        mg.setMaxLocals();
        mg.update();

        remove_local_variable_type_table(mg);
    }

    public void create(OutputStream out) throws IOException {
        createConstructor();
        createMainMethod();
        JavaClass jc = _cg.getJavaClass();
        jc.dump(out);
    }

    private void createConstructor() {
        InstructionList il = new InstructionList();
        MethodGen method = new MethodGen(Const.ACC_PUBLIC, Type.VOID, Type.NO_ARGS, new String[] {}, "<init>",
                "HelloWorld", il, _cp);

        il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
        il.append(_factory.createInvoke("java.lang.Object", "<init>", Type.VOID, Type.NO_ARGS, Const.INVOKESPECIAL));
        il.append(InstructionFactory.createReturn(Type.VOID));

        // 处理栈映射帧
        processMethod(method);

        _cg.addMethod(method.getMethod());
        il.dispose();
    }

    private void createMainMethod() {
        InstructionList il = new InstructionList();
        MethodGen method = new MethodGen(Const.ACC_PUBLIC | Const.ACC_STATIC, Type.VOID,
                new Type[] { new ArrayType(Type.STRING, 1) }, new String[] { "arg0" }, "main", "HelloWorld", il, _cp);

        il.append(InstructionFactory.createLoad(Type.OBJECT, 0));
        il.append(new PUSH(_cp, 0));
        il.append(InstructionConst.AALOAD);
        il.append(InstructionConst.DUP);
        il.append(InstructionFactory.createStore(Type.OBJECT, 1));
        il.append(_factory.createInvoke("java.lang.String", "hashCode", Type.INT, Type.NO_ARGS, Const.INVOKEVIRTUAL));
        Select lookupswitch_8 = new LOOKUPSWITCH(new int[] { 69609650 }, new InstructionHandle[] { null }, null);
        il.append(lookupswitch_8);
        InstructionHandle ih_28 = il.append(InstructionFactory.createLoad(Type.OBJECT, 1));
        il.append(new PUSH(_cp, "Hello"));
        il.append(_factory.createInvoke("java.lang.String", "equals", Type.BOOLEAN, new Type[] { Type.OBJECT },
                Const.INVOKEVIRTUAL));
        BranchInstruction ifne_34 = InstructionFactory.createBranchInstruction(Const.IFNE, null);
        il.append(ifne_34);
        BranchInstruction goto_37 = InstructionFactory.createBranchInstruction(Const.GOTO, null);
        il.append(goto_37);
        InstructionHandle ih_40 = il.append(_factory.createFieldAccess("java.lang.System", "out",
                new ObjectType("java.io.PrintStream"), Const.GETSTATIC));
        il.append(new PUSH(_cp, "Hello World"));
        il.append(_factory.createInvoke("java.io.PrintStream", "println", Type.VOID, new Type[] { Type.STRING },
                Const.INVOKEVIRTUAL));
        BranchInstruction goto_48 = InstructionFactory.createBranchInstruction(Const.GOTO, null);
        il.append(goto_48);
        InstructionHandle ih_51 = il.append(InstructionFactory.createReturn(Type.VOID));
        lookupswitch_8.setTarget(ih_51);
        lookupswitch_8.setTarget(0, ih_28);
        ifne_34.setTarget(ih_40);
        goto_37.setTarget(ih_51);
        goto_48.setTarget(ih_51);

        // 处理栈映射帧
        processMethod(method);

        _cg.addMethod(method.getMethod());
        il.dispose();
    }

    public static void main(String[] args) throws Exception {
        HelloWorldCreator creator = new HelloWorldCreator();
        creator.create(new FileOutputStream("HelloWorld.class"));
    }
}

关键修改点

  1. 调整处理时机:在每个方法的指令构建完成后、添加到ClassGen前,立即处理栈映射帧,确保指令列表处于有效状态。
  2. 移除事后修改类的逻辑:不再先创建所有方法再批量修改,改为逐个方法生成后即时处理。
  3. 复用常量池:直接使用类的常量池,避免资源不一致问题。

验证

运行修改后的HelloWorldCreator生成class文件,执行以下命令:

java HelloWorld Hello

会正常输出Hello World,且无验证错误。

内容的提问来源于stack exchange,提问作者Nicolas Baumann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 18:35:22