Search code examples
javajava-bytecode-asm

ASM method transformation stackmap frame exception


I'm trying to add to user bytecode from a premain method that adds certain information in String form to a list whenever a new line number node is encountered, although when I run the agent and application jars the following exception occurs:

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 
134
Exception Details:
  Location:
    sketches/UserCode.main([Ljava/lang/String;)V @24: if_icmpge
  Reason:
    Expected stackmap frame at this location.
  Bytecode:
    0x0000000: b200 12b8 0018 b600 1cba 0037 0000 b900
    0x0000010: 2e02 0057 033c 1b07 a200 6eb2 0012 b800
    0x0000020: 18b6 001c ba00 3a00 00b9 002e 0200 57bb
    0x0000030: 0014 59ba 004a 0000 1bb2 0012 b800 18b6
    0x0000040: 001c ba00 4d00 00b9 002e 0200 57b8 0053
    0x0000050: b700 564d b200 12b8 0018 b600 1cba 0059
    0x0000060: 0000 b900 2e02 0057 2cb6 005c b200 12b8
    0x0000070: 0018 b600 1cba 0037 0000 b900 2e02 0057
    0x0000080: 8401 01a7 ff93 b200 12b8 0018 b600 1cba
    0x0000090: 005f 0000 b900 2e02 0057 b1
  Stackmap Table:
    append_frame(@22,Integer)
    chop_frame(@154,1)

Here's the premain's transform method:

public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
                // bootstrap loader is not what we're looking for and will be signified by null
                if (loader == null) {
                    return null;
                }

                ClassNode cn = new ClassNode(ASM9);
                ClassReader cr1 = new ClassReader(classfileBuffer);
                cr1.accept(cn, 0);

                ...

                // make the synchronized list field
                cn.fields.add(new FieldNode(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_VOLATILE,
                        listName, "Ljava/util/List;", null, null));

                // go through all MethodNodes and all instruction lists in methods and add synchronized list insert op
                // also, add initialisation for synchronized list (may include creating a static initialiser)
                for (MethodNode mn : cn.methods) {
                    InsnList insns = mn.instructions;
                    if (insns.size() == 0) {
                        continue;
                    }

                    for (AbstractInsnNode node : insns) {
                        if (node instanceof LineNumberNode) {
                            InsnList addedInsns = new InsnList();

                            //TODO: fix this
                            addedInsns.add(new FieldInsnNode(GETSTATIC, cn.name, "list",
                                    "Ljava/util/List;"));
                            addedInsns.add(new MethodInsnNode(INVOKESTATIC, "java/lang/Thread",
                                    "currentThread", "()Ljava/lang/Thread;", false));
                            addedInsns.add(new MethodInsnNode(INVOKEVIRTUAL, "java/lang/Thread",
                                    "getId", "()J", false));
                            addedInsns.add(new InvokeDynamicInsnNode("makeConcatWithConstants",
                                    "(J)Ljava/lang/String;",
                                    new Handle(H_INVOKESTATIC,
                                            "java/lang/invoke/StringConcatFactory",
                                            "makeConcatWithConstants",
                                            "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
                                            false),
                                    "\u0001|" + ((LineNumberNode) node).line + "|" + className));
                            addedInsns.add(new MethodInsnNode(INVOKEINTERFACE, "java/util/List",
                                    "add", "(Ljava/lang/Object;)Z", true));
                            addedInsns.add(new InsnNode(POP));

                            insns.insert(node.getPrevious(), addedInsns);

                        }
                    }

                }

                ClassWriter cw1 = new ClassWriter(ClassWriter.COMPUTE_MAXS);
                cn.accept(cw1);
                return cw1.toByteArray();

            }

I've seen answers to previous questions where the exception message was more or less similar, however, the fault in those cases seemed more obvious to me at least (i.e. a LOAD instruction with no corresponding STORE). I've also looked through the ASM4 user guide, although I think I'm not quite grasping discussion about stackmap frames...

Any advice on how this can be sorted would be appreciated - the more thorough the explanation the better; I would like to be able to understand and apply advice for any such issues I might encounter in future.


Solution

  • It's not the prettiest, but I think this is one way of implementing what Holger is suggesting:

    for (MethodNode mn : cn.methods) {
        InsnList insns = mn.instructions;
        if (insns.size() == 0) {
            continue;
        }
    
        int lineNum = -1;
        int l1 = -1;
        int l2 = -1;
        AbstractInsnNode node;
        int numAdded;
        for (int i = 0; i < insns.size(); i++) {
            node = insns.get(i);
            if (node instanceof LineNumberNode) {
                lineNum = ((LineNumberNode) node).line;
            } else if (node instanceof LabelNode) {
                if (l1 == -1) {
                    l1 = i;
                } else {
                    l2 = i;
                }
            } else if (node instanceof FrameNode) {
                l1 = i;
            }
            if (lineNum > -1 && l1 < l2) {
                InsnList addedInsns = new InsnList();
    
                // bytecode insertion code
    
                numAdded = addedInsns.size(); // get this before inserting into insns as the insert operation empties addedInsns
                insns.insert(insns.get(l1), addedInsns);
                lineNum = -1;
    
                i += numAdded - 1; // -1 to counteract i incrementing with next iteration
                l1 = -1;
                l1 = -1;
            }
    
        }
    
    }