gpt4 book ai didi

java - 重新定义从另一个编译方法调用的方法的方法签名

转载 作者:搜寻专家 更新时间:2023-10-31 20:34:24 25 4
gpt4 key购买 nike

我正在尝试用具有不同返回类型的不同方法替换一个方法,但似乎我能成功做到这一点的唯一方法是让所有具有被替换方法调用站点的方法的源代码.我什至尝试检测调用方法以调用新的替换方法,但我得到一个 VerifyError(操作数堆栈上的类型错误)。有没有办法在重建依赖方法时只使用字节码而不依赖源码?

功能示例(具有源代码依赖性)

public class OverrideTest {  
final ClassPool POOL = ClassPool.getDefault();

class Foo {
public Integer getFoo() { return new Integer(1); }
public String doA() { return getFoo().toString(); }
}

class FooReplacement {
public String getFoo() { return "A"; }
}

@Test
public void d() throws Throwable {
CtClass srcClass = POOL.getCtClass(Foo.class.getName());
CtClass extClass = POOL.getCtClass(FooReplacement.class.getName());
CtClass d = POOL.makeClass("Derp");
CtClass STRING = POOL.get("java.lang.String");
CtClass INT = POOL.get("java.lang.Integer");
{
CtMethod doA1 = srcClass.getMethod("doA", Descriptor.ofMethod(STRING, new CtClass[0]));
CtMethod getFoo1 = srcClass.getMethod("getFoo", Descriptor.ofMethod(INT, new CtClass[0]));
CtMethod getFoo = new CtMethod(INT, "getFoo", new CtClass[0], d);
CtMethod doA = new CtMethod(STRING, "doA", new CtClass[0], d);
d.addMethod(doA);
d.addMethod(getFoo);
doA.setBody(doA1, null);
getFoo.setBody(getFoo1, null);
d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);

d.removeMethod(getFoo);
CtMethod getFooReplaced = new CtMethod(STRING, "getFoo", new CtClass[0], d);
d.addMethod(getFooReplaced);
CtMethod getFooReplaced1 = extClass.getMethod("getFoo", Descriptor.ofMethod(STRING, new CtClass[0]));
getFooReplaced.setBody(getFooReplaced1, null);

doA.setBody("{ return getFoo().toString(); }");
d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);
}
{
Class<?> c = d.toClass();
Constructor<?> ctor = c.getConstructor();
Object derp = ctor.newInstance();
Method getFoo = derp.getClass().getMethod("getFoo");
Method doA = derp.getClass().getMethod("doA");
Object doResult = doA.invoke(derp);
Object getResult = getFoo.invoke(derp);
assertEquals("A", getResult);
assertEquals("A", doResult);
}
}
}

非功能示例(VerifyError)

public class OverrideTest {  
final ClassPool POOL = ClassPool.getDefault();


class Foo {
public Integer getFoo() { return new Integer(1); }
public String doA() { return getFoo().toString(); }
}


class FooReplacement {
public String getFoo() { return "A"; }
}


@Test
public void d() throws Throwable {
CtClass srcClass = POOL.getCtClass(Foo.class.getName());
CtClass extClass = POOL.getCtClass(FooReplacement.class.getName());
CtClass d = POOL.makeClass("Derp");
CtClass STRING = POOL.get("java.lang.String");
CtClass INT = POOL.get("java.lang.Integer");
{
CtMethod doA1 = srcClass.getMethod("doA", Descriptor.ofMethod(STRING, new CtClass[0]));
CtMethod getFoo1 = srcClass.getMethod("getFoo", Descriptor.ofMethod(INT, new CtClass[0]));
CtMethod getFoo = new CtMethod(INT, "getFoo", new CtClass[0], d);
CtMethod doA = new CtMethod(STRING, "doA", new CtClass[0], d);
d.addMethod(doA);
d.addMethod(getFoo);
doA.setBody(doA1, null);
getFoo.setBody(getFoo1, null);
d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);


CtMethod tempMethod = new CtMethod(getFoo.getReturnType(), "tempFoo", new CtClass[0], d);
d.addMethod(tempMethod);
doA.instrument(new MethodReplacer(getFoo, tempMethod));
d.removeMethod(getFoo);


CtMethod getFooReplaced = new CtMethod(STRING, "getFoo", new CtClass[0], d);
d.addMethod(getFooReplaced);
CtMethod getFooReplaced1 = extClass.getMethod("getFoo", Descriptor.ofMethod(STRING, new CtClass[0]));
getFooReplaced.setBody(getFooReplaced1, null);


doA.instrument(new MethodReplacer(tempMethod, getFooReplaced));
d.removeMethod(tempMethod);
d.removeMethod(doA);
CtMethod doA2 = new CtMethod(STRING, "doA", new CtClass[0], d);
d.addMethod(doA2);
doA2.setBody(doA, null);
d.setModifiers(d.getModifiers() & ~Modifier.ABSTRACT);
}
{
Class<?> c = d.toClass();
Constructor<?> ctor = c.getConstructor();
Object derp = ctor.newInstance();
Method getFoo = derp.getClass().getMethod("getFoo");
Method doA = derp.getClass().getMethod("doA");
Object doResult = doA.invoke(derp);
Object getResult = getFoo.invoke(derp);
assertEquals("A", getResult);
assertEquals("A", doResult);
}
}


class MethodReplacer extends ExprEditor {
private CtMethod replacedMethod;
private CtMethod replacement;


MethodReplacer(CtMethod replacedMethod, CtMethod replacement) {
this.replacedMethod = replacedMethod;
this.replacement = replacement;
}


@Override
public void edit(MethodCall mcall) throws CannotCompileException {
CtClass declaringClass = replacedMethod.getDeclaringClass();
try {
CtMethod m = mcall.getMethod();
if (declaringClass.equals(m.getDeclaringClass()) && m.equals(replacedMethod))
mcall.replace("$_ = " + replacement.getName()+"($$);");
} catch (NotFoundException e) {
throw new RuntimeException("Unable to instrument a dependent method call to " + replacedMethod.getName(), e);
}
}


}
}

最佳答案

当你用一个方法编译一个类时:

class Foo {

Object bar() { }
}

这个方法的描述符是方法签名的一部分,编译后看起来像这样:

()Ljava/lang/Object;

注意返回类型是方法签名的一部分!因此,对该方法的任何调用都是显式地针对返回类型 Object 实例的方法进行的。如果您将返回类型更改为例如 Integer,则描述符将改为 ()Ljava/lang/Integer;

如果你已经编译了一个类 Qux 来调用 Foo 的方法,带有 ()Ljava/lang/Object; 描述符但是然后将 Foo 中的方法更改为返回一个 Integer,如果不重新编译 Qux,JVM 将无法分派(dispatch)方法调用:对于 JVM, Qux 正在调用的方法不再存在。当您使用 javassist 之类的工具仅“重新编译”单个方法而不重新编译调用第一个方法的方法时,同样的问题适用于 Foo 中定义的另一个方法。这是验证者在您的示例中提示的问题。

但是,Java 编译器知道用于此目的的桥接方法的概念,其中 Foo 看起来像是代表生成的 Java 字节码的伪 Java 代码。

class Foo {

Object bar() { this.[Integer]bar(); }

Integer bar() { }
}

编译后,其中 this.[Integer]bar(); 表示对返回 Integerbar 方法的调用。 [Object]bar() 方法表示此上下文中的桥接方法。您可以通过首先重新定义方法签名以返回不同的类型并另外添加具有原始返回类型的桥接方法来自己模拟此类桥接方法的创建。

关于java - 重新定义从另一个编译方法调用的方法的方法签名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22546929/

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