Sybuser
Sybuser

Reputation: 1382

How do I create a class with bcel and bcel-util which passes the verifier?

I have a simple class that I'm trying to re-create programatically with BCEL library.

public class HelloWorld {

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

I have a build.gradle that looks like this:

plugins {
    id 'java'
}

repositories {
    mavenCentral()
}

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

First thing I do is generate a class generator class with the help of the main class org.apache.bcel.util.BCELifier with argument HelloWorld. It prints source code to stdout. The generated class HelloWorldCreator is also a main that I can run and it creates a HelloWorld.class. When I run this new HelloWorld class, I have this verifier error:

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)

After a bit of research, I found out there is side library bcel-util that makes up for lack of stackmap frames in class generated by bcel.

BCEL ought to automatically build and maintain the StackMapTable in a manner similar to the LineNumberTable and the LocalVariableTable. However, for historical reasons, it does not.

http://plumelib.org/bcel-util/api/org/plumelib/bcelutil/InstructionListUtils.html

Trying to follow the same piece of code indicated on this link, I came up with this code :

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.classfile.Method;
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);
    }

    void modifyClass(JavaClass jc) {
        ClassGen cg = new ClassGen(jc);
        String classname = cg.getClassName();
        // save ConstantPool for use by StackMapUtils
        pool = cg.getConstantPool();

        for (Method m : cg.getMethods()) {
            try {
                MethodGen mg = new MethodGen(m, classname, pool);
                // Get the instruction list and skip methods with no instructions
                InstructionList il = mg.getInstructionList();
                if (il == null) {
                    continue;
                }

                // Get existing StackMapTable (if present)
                set_current_stack_map_table(mg, cg.getMajor());
                fix_local_variable_table(mg);

                // Create a map of Uninitialized_variable_info offsets to
                // InstructionHandles.
                build_unitialized_NEW_map(il);

                // Update the Uninitialized_variable_info offsets before
                // we write out the new StackMapTable.
                update_uninitialized_NEW_offsets(il);
                create_new_stack_map_attribute(mg);

                // Update the instruction list
                mg.setInstructionList(il);
                mg.update();

                // Update the max stack
                mg.setMaxStack();
                mg.setMaxLocals();
                mg.update();

                remove_local_variable_type_table(mg);

                // Update the method in the class
                cg.replaceMethod(m, mg.getMethod());

            } catch (Throwable t) {
                throw new Error("Unexpected error processing " + classname + "." + m.getName(), t);
            }
        }
    }

    public void create(OutputStream out) throws IOException {
        createMethod_0();
        createMethod_1();
        JavaClass jc = _cg.getJavaClass();
        modifyClass(jc);
        jc.dump(out);
    }

    private void createMethod_0() {
        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));
        method.setMaxStack();
        method.setMaxLocals();
        _cg.addMethod(method.getMethod());
        il.dispose();
    }

    private void createMethod_1() {
        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);
        method.setMaxStack();
        method.setMaxLocals();
        _cg.addMethod(method.getMethod());
        il.dispose();
    }

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

However, when running this new updated HelloWorldCreator, I still have the same verifier issue.

Upvotes: 0

Views: 416

Answers (0)

Related Questions