title: Byte buddy stack manipulation: how to work with local variables and if statements
body: <p>Generally, the questions are: </p>
  
  <ul>
  <li>How and at which moment ByteBuddy generates local variables table and stackmap frames? </li>
  <li>What is the correct way to work with local variables and generate if statements in ByteBuddy's <code>Implementation</code> API?</li>
  </ul>
  
  <p>Details:</p>
  
  <p>I am using bytebuddy for generating equals methods of some classes. For that purpose, I am using custom implementation of <code>net.bytebuddy.implementation.Implementation</code>. In theory, the bytecode I am planning to generate is supposed to have almost the following semantics:</p>
  
  <pre><code>@Override
  public boolean equals(Object obj) {
      if (this == obj) {
          return true;
      }
      if (obj == null) {
          return false;
      }
      if (getClass() != obj.getClass()) {
          return false;
      }
      final T other = (T) obj;
      if (this.a != other.a) {
          return false;
      }
      if (!Objects.equals(this.b, other.b)) {
          return false;
      }
      return true;
  }
  </code></pre>
  
  <p>The code above has one local variable and several <em>if</em>s. I haven't found official <code>StackManipulation</code>'s for both of them, so:</p>
  
  <ul>
  <li>For working with locals, I use <code>MethodVariableAccess.REFERENCE.loadFrom</code> and <code>MethodVariableAccess.REFERENCE.storeAt</code></li>
  <li>For generating if statements, I use custom implementations of <code>StackManipulation</code>s</li>
  </ul>
  
  <p>like:</p>
  
  <pre><code>interface Branching extends StackManipulation {
      @Override
      default boolean isValid() {
          return true;
      }
  
      class Mark implements Branching {
  
          private final Label label;
  
          public Mark(Label label) {
              this.label = label;
          }
  
          @Override
          public final Size apply(MethodVisitor mv, Implementation.Context ctx) {
              mv.visitLabel(label);
              return new Size(0, 0);
          }
      }
  
      class IfNe implements Branching {
  
          private final Label label;
  
          public IfNe(Label label) {
              this.label = label;
          }
  
          @Override
          public final Size apply(MethodVisitor mv, Implementation.Context ctx) {
              mv.visitJumpInsn(Opcodes.IFNE, label);
              return new Size(-2, 0);
          }
      }
  }
  </code></pre>
  
  <p>It seems like I am doing it wrong, because the generated bytecode misses both local variables table and stackmap frames. And it doesn't pass verification of course, complaining that "Expecting a stackmap frame at branch target X".</p>
  
  <p>UPDATE:</p>
  
  <p>I think it is worth to add an example here. Initial case I was talking about was quite large, so I wrote a module anew to demonstrate an issue. It's quite large, but I can't imagine how to make it smaller:</p>
  
  <pre><code>package com.xxx.proba.bytebuddy;
  
  import java.io.File;
  import java.io.PrintStream;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import net.bytebuddy.ByteBuddy;
  import net.bytebuddy.description.field.FieldDescription;
  import net.bytebuddy.description.method.MethodDescription;
  import net.bytebuddy.description.modifier.Visibility;
  import net.bytebuddy.dynamic.DynamicType;
  import net.bytebuddy.dynamic.scaffold.InstrumentedType;
  import net.bytebuddy.implementation.Implementation;
  import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
  import net.bytebuddy.implementation.bytecode.StackManipulation;
  import net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
  import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
  import net.bytebuddy.implementation.bytecode.constant.TextConstant;
  import net.bytebuddy.implementation.bytecode.member.FieldAccess;
  import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
  import net.bytebuddy.implementation.bytecode.member.MethodReturn;
  import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
  import net.bytebuddy.jar.asm.Label;
  import net.bytebuddy.jar.asm.MethodVisitor;
  import net.bytebuddy.jar.asm.Opcodes;
  
  
  /**
   * Assuming that I want to generate the class like this one
   * 
   * @author skapral
   */
  class ExampleClass {
      public void main(String[] args) {
          if (args[0].equals("")) {
              System.out.println("a");
          } else {
              System.out.println("b");
          }
      }
  }
  
  public class Main {
      private final static Method EQUALS;
      private final static Method PRINTLN;
      private final static Field SYSTEM_OUT;
  
      static {
          try {
              EQUALS = Object.class.getMethod("equals", Object.class);
              PRINTLN = PrintStream.class.getMethod("println", String.class);
              SYSTEM_OUT = System.class.getField("out");
          } catch (Exception ex) {
              throw new RuntimeException(ex);
          }
      }
  
      public static void main(String[] args) throws Exception {
          DynamicType.Unloaded&lt;Object&gt; unloaded = new ByteBuddy()
                  .subclass(Object.class)
                  .name("com.echelon.proba.bytebuddy.ExampleClassGenerated")
                  .defineMethod("main", void.class, Visibility.PUBLIC)
                  .withParameter(String[].class)
                  .intercept(new Implementation() {
                      @Override
                      public ByteCodeAppender appender(Implementation.Target implementationTarget) {
                          return new ByteCodeAppender() {
                              @Override
                              public ByteCodeAppender.Size apply(MethodVisitor mv, Implementation.Context ctx, MethodDescription md) {
                                  Label ifLabel = new Label();
                                  Label elseLabel = new Label();
  
                                  StackManipulation.Size size = new StackManipulation.Compound(
                                          MethodVariableAccess.REFERENCE.loadFrom(1),
                                          IntegerConstant.ZERO,
                                          ArrayAccess.REFERENCE.load(),
                                          new TextConstant(""),
                                          MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(EQUALS)), 
                                          new IfEq(ifLabel),
                                          FieldAccess.forField(new FieldDescription.ForLoadedField(SYSTEM_OUT)).read(),
                                          new TextConstant("a"),
                                          MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(PRINTLN)),
                                          new GoTo(elseLabel),
                                          new Mark(ifLabel),
                                          FieldAccess.forField(new FieldDescription.ForLoadedField(SYSTEM_OUT)).read(),
                                          new TextConstant("b"),
                                          MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(PRINTLN)),
                                          new Mark(elseLabel),
                                          MethodReturn.VOID
                                  ).apply(mv, ctx);
                                  return new Size(size.getMaximalSize(), md.getStackSize());
                              }
                          };
                      }
  
                      @Override
                      public InstrumentedType prepare(InstrumentedType instrumentedType) {
                          return instrumentedType;
                      }
                  })
                  .make();
          unloaded.saveIn(new File("/tmp/aaa")); /* Preserve it for future investigation by javap */
          Object obj = unloaded.load(Main.class.getClassLoader()).getLoaded().newInstance();
          obj.getClass().getMethod("main", String[].class).invoke(obj, new String[] {"aaa"}); /* Trigger class loading and verification */
      }
  }
  
  
  class IfEq implements StackManipulation {
      private final Label label;
  
      public IfEq(Label label) {
          this.label = label;
      }
  
      @Override
      public boolean isValid() {
          return true;
      }
  
      @Override
      public StackManipulation.Size apply(MethodVisitor mv, Implementation.Context ctx) {
          mv.visitJumpInsn(Opcodes.IFEQ, label);
          return new StackManipulation.Size(-1, 0);
      }
  }
  
  class GoTo implements StackManipulation {
      private final Label label;
  
      public GoTo(Label label) {
          this.label = label;
      }
  
      @Override
      public boolean isValid() {
          return true;
      }
  
      @Override
      public StackManipulation.Size apply(MethodVisitor mv, Implementation.Context ctx) {
          mv.visitJumpInsn(Opcodes.GOTO, label);
          return new StackManipulation.Size(0, 0);
      }
  }
  
  class Mark implements StackManipulation {
      private final Label label;
  
      public Mark(Label label) {
          this.label = label;
      }
  
      @Override
      public boolean isValid() {
          return true;
      }
  
      @Override
      public StackManipulation.Size apply(MethodVisitor mv, Implementation.Context ctx) {
          mv.visitLabel(label);
          return new StackManipulation.Size(0, 0);
      }
  }
  </code></pre>
  
  <p>In that example, in Main::main method, I'm trying to generate simple ExampleClass by bytebuddy. On attempt to load it and call a method, I got VerifyError.</p>
  
  <pre><code>  Location:
      com/echelon/proba/bytebuddy/ExampleClassGenerated.main([Ljava/lang/String;)V @8: ifeq
    Reason:
      Expected stackmap frame at this location.
    Bytecode:
      0x0000000: 2b03 3212 08b6 000c 9900 0eb2 0012 1214
      0x0000010: b600 1aa7 000b b200 1212 1cb6 001a b1  
  
      at java.lang.Class.getDeclaredConstructors0(Native Method)
      at java.lang.Class.privateGetDeclaredConstructors(Class.java:2671)
      at java.lang.Class.getConstructor0(Class.java:3075)
      at java.lang.Class.newInstance(Class.java:412)
      at com.xxx.proba.bytebuddy.Main.main(Main.java:103)
  </code></pre>
  
  <p>UPDATE: Just to summarize: this simple AsmVisitorWrapper helped me:</p>
  
  <pre><code>public class EnableFramesComputing implements AsmVisitorWrapper {
      @Override
      public final int mergeWriter(int flags) {
          return flags | ClassWriter.COMPUTE_FRAMES;
      }
  
      @Override
      public final int mergeReader(int flags) {
          return flags | ClassWriter.COMPUTE_FRAMES;
      }
  
      @Override
      public final ClassVisitor wrap(TypeDescription td, ClassVisitor cv, Implementation.Context ctx, TypePool tp, FieldList&lt;FieldDescription.InDefinedShape&gt; fields, MethodList&lt;?&gt; methods, int wflags, int rflags) {
          return cv;
      }
  }
  </code></pre>
  
  <p>It can be placed in action by calling visit on DynamicType.Builder, like:</p>
  
  <pre><code>DynamicType.Unloaded&lt;Object&gt; unloaded = new ByteBuddy()
              .subclass(Object.class)
              .visit(new EnableFramesComputing())
              ...
  </code></pre>