Reputation: 1382
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