2017-11-23 9 views
2

Sagen wir, ich habe eine einfacheVerifyException wenn ein wenn stmt ein Verfahren zur Laufzeit das Hinzufügen

class C$Manipulatables{ 
    public Wrapper get(long v, String f){ 
     if(v == 0){ 
      return new Wrapper(0); 
     }else{ 
      throw new RuntimeException("unacceptable: v not found"); 
     } 
    } 
} 

Ich möchte jetzt die get in C$Manipulatables, S. T. neu zu definieren es liest

class C$Manipulatables{ 
    public Wrapper get(long v, String f){ 
     if(v == 1){ return null; } 
     if(v == 0){ 
      return new Wrapper(0); 
     }else{ 
      throw new RuntimeException("unacceptable: v not found"); 
     } 
    } 
} 

Dies wird eine Nullpointer-Ausnahme auslösen, wenn ich versuche, den neuen Wert zu verwenden, aber das ist in Ordnung - für jetzt nur Bestätigung möchte ich, dass der neue Code geladen wird.

wir den Code mit ASM hinzufügen (Ich werde die vollständige Kopie-verpastbarem-Code am Ende dieses Beitrags hinzufügen, so dass ich nur auf die relevanten Teile Zoomen, hier):

class AddingMethodVisitor extends MethodVisitor implements Opcodes{ 
     int v; 
     public AddingMethodVisitor(int v, int api, MethodVisitor mv) { 
      super(api, mv); 
      this.v = v; 
     } 

     @Override 
     public void visitCode() { 
      super.visitCode(); 

      mv.visitVarInsn(LLOAD, 1); //arg 1 of method is version number 

      /*if arg1 == the new version*/ 
      mv.visitLdcInsn(v); 
      Label lSkip = new Label(); 

      mv.visitInsn(LCMP); 
      mv.visitJumpInsn(IFNE, lSkip); 

      mv.visitInsn(ACONST_NULL); 
      mv.visitInsn(ARETURN); 

      /*else*/ 
      mv.visitLabel(lSkip); 
      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 

     } 
    } 

und laden Sie die Klasse mit ByteBuddy (wieder voll Code am Ende der Post):

 ClassReader cr; 
     try { 
      /*note to self: don't forget the ``.getClassLoader()``*/ 
      cr = new ClassReader(manipsClass.getClassLoader().getResourceAsStream(manipsClass.getName().replace('.','/') + ".class")); 
     }catch(IOException e){ 
      throw new RuntimeException(e); 
     } 

     ClassWriter cw = new ClassWriter(cr, 0); 

     VersionAdder adder = new VersionAdder(C.latest + 1,Opcodes.ASM5,cw); 

     cr.accept(adder,0); 

     System.out.println("reloading C$Manipulatables class"); 
     byte[] bytes = cw.toByteArray(); 

     ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(manipsClass.getName(), bytes); 

     new ByteBuddy() 
       .redefine(manipsClass,classFileLocator) 
       .name(manipsClass.getName()) 
       .make() 
       .load(C.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()) 
     ; 

     C.latest++; 
     System.out.println("RELOADED"); 
    } 
} 

Dies schlägt fehl.

got 0 
reloading C$Manipulatables class 
Exception in thread "main" java.lang.VerifyError 
    at sun.instrument.InstrumentationImpl.redefineClasses0(Native Method) 
    at sun.instrument.InstrumentationImpl.redefineClasses(InstrumentationImpl.java:170) 
    at net.bytebuddy.dynamic.loading.ClassReloadingStrategy$Strategy$1.apply(ClassReloadingStrategy.java:261) 
    at net.bytebuddy.dynamic.loading.ClassReloadingStrategy.load(ClassReloadingStrategy.java:171) 
    at net.bytebuddy.dynamic.TypeResolutionStrategy$Passive.initialize(TypeResolutionStrategy.java:79) 
    at net.bytebuddy.dynamic.DynamicType$Default$Unloaded.load(DynamicType.java:4456) 
    at redefineconcept.CInserter.addNext(CInserter.java:60) 
    at redefineconcept.CInserter.run(CInserter.java:22) 
    at redefineconcept.CInserter.main(CInserter.java:16) 

Process finished with exit code 1 

In der Tat, dies nicht gelingt, auch wenn ich die return null Anw Generation Kommentar (wie ich in dem vollständigen Code habe unten angegeben).

Anscheinend java mag einfach nicht so, wie ich meine IF aufgebaut haben, auch wenn es im Wesentlichen der Code ist habe ich, wenn ich asmifier auf

verwendet
public class B { 

    public Object run(long version, String field){ 
     if(version == 2) { 
      return null; 
     } 
     return null; 
    } 
} 

die

{ 
mv = cw.visitMethod(ACC_PUBLIC, "run", "(JLjava/lang/String;)Ljava/lang/Object;", null, null); 
mv.visitCode(); 
mv.visitVarInsn(LLOAD, 1); 
mv.visitLdcInsn(new Long(2L)); 
mv.visitInsn(LCMP); 
Label l0 = new Label(); 
mv.visitJumpInsn(IFNE, l0); 
mv.visitInsn(ACONST_NULL); 
mv.visitInsn(ARETURN); 
mv.visitLabel(l0); 
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 
mv.visitInsn(ACONST_NULL); 
mv.visitInsn(ARETURN); 
mv.visitMaxs(4, 4); 
mv.visitEnd(); 
} 

ergab Ich habe einfach alles weg nach

mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 

weil danach das schon exis Teil der Methode folgt.

Ich glaube wirklich, dass es etwas über das , das Java nicht mag.

Nehmen wir an, ich ändere meine visitCode s.t. es liest

 public void visitCode() { 
      super.visitCode(); 

      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); 
      mv.visitLdcInsn("Work, you ..!"); 
      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); 

//   mv.visitVarInsn(LLOAD, 1); //arg 1 of method is version number 
// 
//   /*if arg1 == the new version*/ 
//   mv.visitLdcInsn(v); 
//   Label lSkip = new Label(); 
// 
//   mv.visitInsn(LCMP); 
//   mv.visitJumpInsn(IFNE, lSkip); 
// 
////   mv.visitInsn(ACONST_NULL); 
////   mv.visitInsn(ARETURN); 
// 
//   /*else*/ 
//   mv.visitLabel(lSkip); 
//   mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 

     } 

Dann funktioniert die Neudefinition. Ich bekomme die erwartete Ausnahme, weil der Code durch den ursprünglichen if-else-Block fällt, der die neue Versionsnummer nicht verarbeiten kann, aber ich bekomme zumindest die Ausgabe.

got 0 
reloading C$Manipulatables class 
RELOADED 
Work, you ..! 
Exception in thread "main" java.lang.RuntimeException: unacceptable: v not found 
    at redefineconcept.C$Manipulatables.get(C.java:27) 
    at redefineconcept.C.get(C.java:10) 
    at redefineconcept.CInserter.run(CInserter.java:23) 
    at redefineconcept.CInserter.main(CInserter.java:16) 

Ich würde sehr gerne einige Hilfe bei der Lösung dieses Problems zu schätzen wissen. Was ist die korrekte Art, eine neue if stmt einzufügen, die Java akzeptiert?

FULL CODE

C.java

(Beachten Sie, dass die Klasse C$Manipulatables notwendig ist, weil ByteBuddy nicht Klassen neu definieren können, die statisch initialisers haben.

)
package redefineconcept; 

public class C { 
    public static volatile int latest = 0; 

    public static final C$Manipulatables manips = new C$Manipulatables(); 

    public int get(){ 
     int v = latest; 
     return manips.get(v,"").value; 
    } 
} 

class Wrapper{ 
    int value; 

    public Wrapper(int value){ 
     this.value = value; 
    } 
} 

class C$Manipulatables{ 
    public Wrapper get(long v, String f){ 
     if(v == 0){ 
      return new Wrapper(0); 
     }else{ 
      throw new RuntimeException("unacceptable: v not found"); 
     } 
    } 
} 

CInserter.java

package redefineconcept; 

import net.bytebuddy.ByteBuddy; 
import net.bytebuddy.agent.ByteBuddyAgent; 
import net.bytebuddy.dynamic.ClassFileLocator; 
import net.bytebuddy.dynamic.loading.ClassReloadingStrategy; 
import net.bytebuddy.jar.asm.*; 

import java.io.IOException; 

    public class CInserter { 
     public static void main(String[] args) { 

      ByteBuddyAgent.install(); 

      new CInserter().run(); 
     } 

     private void run(){ 
      C c = new C(); 
      System.out.println("got " + c.get()); 
      addNext(); 
      System.out.println("got " + c.get()); //should trigger nullptr exception 
     } 

     private void addNext(){ 

      Object manips; 
      String manipsFld = "manips"; 

      try { 
       manips = C.class.getDeclaredField(manipsFld).get(null); 
      }catch(NoSuchFieldException | IllegalAccessException e){ 
       throw new RuntimeException(e); 
      } 

      Class<?> manipsClass = manips.getClass(); 
      assert(manipsClass.getName().equals("redefineconcept.C$Manipulatables")); 


      ClassReader cr; 
      try { 
       /*note to self: don't forget the ``.getClassLoader()``*/ 
       cr = new ClassReader(manipsClass.getClassLoader().getResourceAsStream(manipsClass.getName().replace('.','/') + ".class")); 
      }catch(IOException e){ 
       throw new RuntimeException(e); 
      } 

      ClassWriter cw = new ClassWriter(cr, 0); 

      VersionAdder adder = new VersionAdder(C.latest + 1,Opcodes.ASM5,cw); 

      cr.accept(adder,0); 

      System.out.println("reloading C$Manipulatables class"); 
      byte[] bytes = cw.toByteArray(); 

      ClassFileLocator classFileLocator = ClassFileLocator.Simple.of(manipsClass.getName(), bytes); 

      new ByteBuddy() 
        .redefine(manipsClass,classFileLocator) 
        .name(manipsClass.getName()) 
        .make() 
        .load(C.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent()) 
      ; 

      C.latest++; 
      System.out.println("RELOADED"); 
     } 
    } 

class VersionAdder extends ClassVisitor{ 
    private int v; 
    public VersionAdder(int v, int api, ClassVisitor cv) { 
     super(api, cv); 
     this.v = v; 
    } 

    @Override 
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 
     MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 

     if(mv != null && name.equals("get")){ 
      return new AddingMethodVisitor(v,Opcodes.ASM5,mv); 
     } 

     return mv; 
    } 

    class AddingMethodVisitor extends MethodVisitor implements Opcodes{ 
     int v; 
     public AddingMethodVisitor(int v, int api, MethodVisitor mv) { 
      super(api, mv); 
      this.v = v; 
     } 

     @Override 
     public void visitCode() { 
      super.visitCode(); 

      mv.visitVarInsn(LLOAD, 1); //arg 1 of method is version number 

      /*if arg1 == the new version*/ 
      mv.visitLdcInsn(v); 
      Label lSkip = new Label(); 

      mv.visitInsn(LCMP); 
      mv.visitJumpInsn(IFNE, lSkip); 

//   mv.visitInsn(ACONST_NULL); 
//   mv.visitInsn(ARETURN); 

      /*else*/ 
      mv.visitLabel(lSkip); 
      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null); 

     } 
    } 
} 

Antwort

1

Ein Fehler, den ich in Ihrem Code bemerkt ist die folgende Zeile

mv.visitLdcInsn(v);

Die Absicht des Codes zu erstellen und Last eine lange Konstante, aber v hat Typ int, so wird eine ganze Zahl Konstante sein Stattdessen wird ein Typfehler im Bytecode erzeugt, wenn Sie ihn in der nächsten Zeile mit lcmp vergleichen. visitLdcInsn erstellt einen anderen Konstantentyp abhängig vom Typ des übergebenen Objekts. Das Argument muss also genau den gewünschten Typ haben.

Nebenbei gesagt, Sie brauchen LDC an erster Stelle nicht, um eine lange Konstante von Wert 1 zu erstellen, da es eine dedizierte Bytecode-Anweisung dafür gibt, lconst_1. In ASM sollte dies so etwas wie visitInsn(LCONST_1);

+0

Das war in der Tat das Problem, hier. Vielen Dank. Ich sollte jedoch beachten, dass die Verwendung von '' LCONST'' keine Option ist, da der Code mit beliebigen Werten umgehen soll. Ich denke, ich könnte überprüfen, ob dieser Wert den Bereich überschreitet, für den wir eine dedizierte Anweisung "CONST" haben, oder nicht, aber der Fehler wäre nicht korrekt. – User1291

+2

Sie müssen nicht über die Bereiche und die effizienteste Anweisung nachdenken, wenn Sie den von "ClassWriter" zurückgegebenen 'MethodVisitor 'in einen [' Anweisungsadapter] (http://asm.ow2.org/asm50/javadoc/user) einfügen /?org/objectweb/asm/commons/InstructionAdapter.html). Dann können Sie 'iconst (value)' verwenden und es wird darauf geachtet, entweder 'iconst_x',' bipush', 'sipush' oder' ldc' nach Bedarf zu generieren. – Holger

Verwandte Themen