gpt4 book ai didi

java - 在运行时向方法添加 if stmt 时出现 VerifyException

转载 作者:行者123 更新时间:2023-11-30 10:22:07 25 4
gpt4 key购买 nike

假设我有一个简单的

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

我现在想在 C$Manipulatables 中重新定义 get,s.t.上面写着

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

如果我尝试使用新值,这将触发空指针异常,但没关系 - 现在我只想确认新代码已加载。

我们使用 ASM 添加代码(我将在这篇文章的底部添加完整的可复制粘贴代码,所以我只是放大相关部分,在这里):

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

}
}

并使用 ByteBuddy 重新加载类(同样,帖子底部的完整代码):

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

这失败了。

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

事实上,当我评论 return null stmt 生成时(正如我在下面提供的完整代码中所做的那样),这甚至会失败。

显然,java 只是不喜欢我构造我的 IF 的方式,即使它本质上是我在

上使用 asmifier 时得到的代码
public class B {

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

产生了

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

我只是简单地离开了之后的一切

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

因为在那之后,方法的已经存在的部分紧随其后。

我确实相信 Java 不喜欢 visitFrame 的某些方面。

假设我更改了我的 visitCode s.t.上面写着

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

}

然后重新定义工作。我得到了预期的异常,因为代码落入了无法处理新版本号的原始 if-else block ,但至少我确实得到了输出。

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)

如果您能帮我解决这个问题,我将不胜感激。插入 java 将接受的新 if stmt 的正确方法是什么?

完整代码

C.java

(注意 C$Manipulatables 类是必需的,因为 ByteBuddy 不能重新定义具有静态初始化器的类。)

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

}
}
}

最佳答案

我在您的代码中注意到的一个错误是以下行

mv.visitLdcInsn(v);

这段代码的目的是创建并加载一个 long 常量,但是 v 的类型是 int,因此将创建一个整型常量,从而创建一个类型将字节码与下一行的 lcmp 进行比较时出现字节码错误。 visitLdcInsn 将根据您传入的对象类型创建不同的常量类型,因此参数必须是您想要的确切类型。

附带说明,您首先不需要 LDC 来创建值为 1 的长整型常量,因为有专门的字节码指令用于此,lconst_1。在 ASM 中,这应该类似于 visitInsn(LCONST_1);

关于java - 在运行时向方法添加 if stmt 时出现 VerifyException,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47459014/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com