は一般的に、質問がある場合にする方法:バイトバディスタック操作:ローカル変数と協力し、文
- どのように、どの瞬間に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.loadFrom
と
MethodVariableAccess.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())
...
ありがとう、私はそれについて考えます。ツールの「ハイレベル」ステータスについて語る。オペコードを分岐するためにそのような差別を持つことは奇妙に見えます。 BytebuddyのStackManipulationは本当に素晴らしい抽象で、ASMの訪問者よりもはるかに良い。そして、実際にはハードケースでは低レベルで作業できるように見えます。そういうわけで私は本当にあなたのことを理解していないのです - なぜ私のケースはByteBuddyが設計していないものとみなされますか? – skapral
とにかく、AsmVisitorWrapperは確かに道のりです。ありがとうございました。私は必要な点検と捜査をすべて行うとすぐにこの回答を受け入れます。 – skapral
私はこの機能を追加することを検討しましたが、多分私はある時点で対応します。これはスタック状態を追跡するという問題を抱えています。良いAPIはしばしば高速ではありませんが、通常は低レベルのAPIを使用する人はそれを必要とします。 –