2017-06-21 5 views
0

は一般的に、質問がある場合にする方法:バイトバディスタック操作:ローカル変数と協力し、文

  • どのように、どの瞬間にByteBuddyは、ローカル変数テーブルとスタックマップフレームを生成?
  • ByteBuddyのImplementation APIで、ローカル変数を使用してif文を生成する正しい方法は何ですか?

詳細:

私は発電のためにbytebuddy使用していますが、いくつかのクラスのメソッドに等しいです。そのために、net.bytebuddy.implementation.Implementationのカスタム実装を使用しています。

@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; 
} 

上記のコードは一つのローカル変数を持っており、いくつかの場合S:理論的には、私が生成することを計画していたバイトコードは、ほぼ以下の意味を持つことになっています。私は、それらの両方のための公式StackManipulationさんを発見していない:

  • 地元の人々と協力のためのステートメントは、私がStackManipulation
のカスタム実装を使用している場合、私は生成するため MethodVariableAccess.REFERENCE.loadFromMethodVariableAccess.REFERENCE.storeAt
  • を使用します

    のような:

    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); 
         } 
        } 
    } 
    

    私はそれが間違ってやっているように生成されたので、それはそうですバイトコードは、ローカル変数テーブルとスタックマップフレームの両方を失います。そして、 "ブランチターゲットXでのスタックマップフレームを期待する"と不平を言いながら、もちろん検証をパスしません。

    UPDATE:

    私はここでは例を追加する価値があると思います。私が話していた初期のケースはかなり大きかったので、問題を示すために新しいモジュールを書きました。それは非常に大きいのですが、私はそれを小さくする方法を想像することはできません。

    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<Object> 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); 
        } 
    } 
    

    その例では、メイン:: mainメソッドでは、私がbytebuddyでシンプルでExampleClassを生成しようとしています。ロードしてメソッドを呼び出そうとすると、私はVerifyErrorを取得しました。

    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) 
    

    UPDATE:ただ要約する:このシンプルなAsmVisitorWrapperは私を助け:

    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<FieldDescription.InDefinedShape> fields, MethodList<?> methods, int wflags, int rflags) { 
         return cv; 
        } 
    } 
    

    それは同様に、DynamicType.Builderに訪問を呼び出すことによってアクションに入れることができます。

    DynamicType.Unloaded<Object> unloaded = new ByteBuddy() 
          .subclass(Object.class) 
          .visit(new EnableFramesComputing()) 
          ... 
    
  • 答えて

    2

    バイトBuddyは、高レベルのバイトコード操作ライブラリを目指しています。下位レベルのバイトコードを作成する場合は、ASMを直接使用してください。これは、そのための優れたツールです。

    ASMは、COMPUTE_FRAMESフラグを設定することによってスタックマップフレームを計算することを提供します。ラッパーを登録せずにそのフラグのみを設定するAsmVisitorWrapperを登録してフラグを設定することができます。

    カスタムバイトコードを作成する場合は、Adviceコンポーネントを検討しましたか?実行時にバイトコードがインライン展開され、適切なパラメータにマップされるプレーンJavaでコードを記述することができます。

    +0

    ありがとう、私はそれについて考えます。ツールの「ハイレベル」ステータスについて語る。オペコードを分岐するためにそのような差別を持つことは奇妙に見えます。 BytebuddyのStackManipulationは本当に素晴らしい抽象で、ASMの訪問者よりもはるかに良い。そして、実際にはハードケースでは低レベルで作業できるように見えます。そういうわけで私は本当にあなたのことを理解していないのです - なぜ私のケースはByteBuddyが設計していないものとみなされますか? – skapral

    +0

    とにかく、AsmVisitorWrapperは確かに道のりです。ありがとうございました。私は必要な点検と捜査をすべて行うとすぐにこの回答を受け入れます。 – skapral

    +0

    私はこの機能を追加することを検討しましたが、多分私はある時点で対応します。これはスタック状態を追跡するという問題を抱えています。良いAPIはしばしば高速ではありませんが、通常は低レベルのAPIを使用する人はそれを必要とします。 –

    関連する問題