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