0

I've generated a simple getter method with ASM in an already existent class.

mv = cn.visitMethod(access,                    // public method
                    "get_" + f.name,           // name
                    "()Ljava/lang/String;",    // descriptor
                    null,                      // signature (null means not generic)
                    null);                     // exceptions (array of strings

mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitFieldInsn(Opcodes.GETFIELD, cn.name, f.name, f.desc);
mv.visitInsn(Opcodes.ARETURN);
mv.visitMaxs(0, 0);

Then I generated the class.

ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cn.accept(cw);

Now, I can access the class via cw.toByteArray(). The problem is when I try the load the class I get an error because the StackMapTable isn't right (it should be, I'm using ClassWriter.COMPUTE_FRAMES ?)

The error

Exception in thread "main" java.lang.VerifyError: Bad local variable type
Exception Details:
  Location:
    a.get_c()Ljava/lang/String; @0: aload_0
  Reason:
    Type top (current frame, locals[0]) is not assignable to reference type
  Current Frame:
    bci: @0
    flags: { }
    locals: { }
    stack: { }
  Bytecode:
    0x0000000: 2ab4 0012 b0   

After this I added a CheckClassAdapter to see what is wrong.

CheckClassAdapter ca = new CheckClassAdapter(cw, false); //Check data flow
ClassReader cr = new ClassReader(cw.toByteArray());
ca.verify(cr, new GenericClassLoader(), true, new PrintWriter(new PrintStream(System.out)));

The output is the following.

org.objectweb.asm.tree.analysis.AnalyzerException: Error at instruction 0: Expected an object reference, but found .
    at org.objectweb.asm.tree.analysis.Analyzer.analyze(Unknown Source)
    at org.objectweb.asm.util.CheckClassAdapter.verify(Unknown Source)
    at me.ffy00.ClassGenerator2.generateSkeleton(ClassGenerator2.java:85)
    at me.ffy00.Test.main(Test.java:25)
Caused by: org.objectweb.asm.tree.analysis.AnalyzerException: Expected an object reference, but found .
    at org.objectweb.asm.tree.analysis.BasicVerifier.copyOperation(Unknown Source)
    at org.objectweb.asm.tree.analysis.BasicVerifier.copyOperation(Unknown Source)
    at org.objectweb.asm.tree.analysis.Frame.execute(Unknown Source)
    ... 4 more
get_c()Ljava/lang/String;
00000 .  :  :     ALOAD 0
00001 ?   :     GETFIELD a.c : Ljava/lang/String;
00002 ?   :     ARETURN

If I take a look to my method get_c() I can see it has ? in some Opcodes (maybe because the first Opcode isn't understood?)

00000 .  :  :     ALOAD 0
00001 ?   :     GETFIELD a.c : Ljava/lang/String;
00002 ?   :     ARETURN

I don't really now what that means but the method doesn't look like it's supposed. I compiled a simple Getter class and the method looks like this.

00000 Getter  :  :     ALOAD 0
00001 Getter  : Getter  :     GETFIELD me/ffy00/Getter.a : Ljava/lang/String;
00002 Getter  : String  :     ARETURN

I already tried to set the class version to 50 because the StackMapTable wasn't implemented yet, but it doesn't appear to change anything. I just set the version variable of the ClassNode to 50. Like this.

cn.version = 50;

And the class looks like this in javap.

public class a
  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_SUPER

Also using javap I can get my method, including the stack size and the local number but the method doesn't have a StackMapTable (yes I'm using -v, the StackMapTable shows in other methods).

  static java.lang.String get_c();
    descriptor: ()Ljava/lang/String;
    flags: ACC_STATIC
    Code:
      stack=1, locals=1, args_size=0
         0: aload_0
         1: getfield      #18                 // Field c:Ljava/lang/String;
         4: areturn

My variable looks like this.

  private static final java.lang.String c;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL

Can some point me to what I need to do to fix the method. I couldn't find any helpful information about how to fix the StackMapTable with ASM.

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
FFY00
  • 82
  • 1
  • 10
  • 2
    Your comment says that `access` is `public method`, but `javap` thinks your method is default-visibility (aka package) _and static_. Only non-static methods get `this` in arg0. – dave_thompson_085 Jul 20 '17 at 14:06
  • Yes, thanks. I totally forgot that. The method body needs to be different for the static getter. – FFY00 Jul 20 '17 at 15:55

1 Answers1

0

As pointed out by dave_thompson_085, the problem is that you accidentally made the method static, but you are still accessing this. Or in bytecode terms, you are trying to load an object out of slot 0, but there is nothing in slot 0 at the start of the method.

ASM only generates the stack frames based on the code you give it. It can't magically generate valid stackframes for invalid code.

Antimony
  • 37,781
  • 10
  • 100
  • 107
  • Thaks! I forgot that. Can you just explain me why we need to load this for non-static getter methods? I'm still learning bytecode but there isn't a lot of information. – FFY00 Jul 20 '17 at 16:00
  • @FFY00 You're accessing a non-static field, which means you need some object to access the field on. The same is true in Java, it's just that the `this` is implicit. – Antimony Jul 20 '17 at 16:11
  • In this specific case, ASM doesn’t generate any stack frames at all, as the method doesn’t contain any branches. The first frame, the `VerifyError` refers to, is implied by the method signature. @FFY00 if you look again at the `VerifyError` carefully, you'll notice that the term “StackMapTable” doesn’t appear anywhere in the message. – Holger Jul 20 '17 at 21:19