如何使用ASM将一个Object类的对象动态转换为方法返回类型?

huangapple go评论86阅读模式
英文:

How to dynamically cast an object (of class Object) to the method return type using ASM?

问题

我已经翻译了您提供的代码部分。请注意,代码中的特殊字符如"可能需要根据上下文替换为正常的引号。

要注意的是,ASM是一个复杂的字节码操作库,错误可能出现在许多地方,包括字节码生成、类型转换等。关于java.lang.VerifyError: Incompatible object argument for function call错误,通常是因为字节码中的类型转换不匹配导致的。以下是您的代码的翻译:

我想要做的是使用ASM修改一个方法
1. 我将一个对象Object类的对象推入堆栈
2. 我想将该对象强制转换为该方法的返回类型
3. 返回转换后的对象

我的方法Visitor适配器中的代码
```java
public void visitCode() {
    mv.visitCode();
    if (needModify){
        // 将所有方法参数打包成一个对象数组并推入堆栈
        ...
        // selfReturnTypeDotClassName是返回类型的点类名
        mv.visitLdcInsn(selfReturnTypeDotClassName);
        // 推入对象(Object类的对象)
        mv.visitMethodInsn(INVOKESTATIC, MyClass, "getOutputObj",
                "([Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", false);

        // 将对象转换为返回类型
        castPeekOnStack(selfReturnType);

        mv.visitInsn(selfReturnType.getOpcode(IRETURN));
    }
}

MyClass 中的 getOutputObj 方法(它尝试将先前记录的Json字符串恢复为对象,使用 Gson):

public static Object getOutputObj(Object[] args, String methodId, String returnTypeDotClassName){
    HashMap<String, String> inOutMap = getInOutMapOfMethod(methodId);
    // GSON 是 Gson 类的一个实例
    String inputJson = GSON.toJson(args);
    String outputJson = inOutMap.get(inputJson);
    return recoverObjFromJson(outputJson, returnTypeDotClassName);
}

public static Object recoverObjFromJson(String outputJson, String returnTypeDotClassName){
    try{
        // 该对象先前被打包为长度为1的对象数组。
        Object obj = GSON.fromJson(outputJson, Object[].class)[0];
        return obj;
    }catch (Exception e){
        e.printStackTrace();
        MyEkstaziAgent.log(String.format("Gson Error: fromJson failed for arguments %s, %s",
                outputJson, returnTypeDotClassName));
        return null;
    }
}

我的第一个版本的 castPeekOnStack 方法:

public void castPeekOnStack(Type targetType){
    switch (targetType.getSort()) {
        // 不确定
        case Type.BOOLEAN:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
            break;
        case Type.BYTE:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
            break;
        case Type.CHAR:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
            break;
        case Type.SHORT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
            break;
        case Type.INT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
            break;
        case Type.FLOAT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
            break;
        case Type.LONG:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
            break;
        case Type.DOUBLE:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
            break;
        case Type.ARRAY:
            mv.visitTypeInsn(CHECKCAST, targetType.getDescriptor());
            break;
        case Type.OBJECT:
            mv.visitTypeInsn(CHECKCAST, targetType.getInternalName());
            break;
    }
}

我在仅具有 int 返回类型的基准上尝试了这段代码。然后我收到了 java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer 错误。我认为当我将对象推送到堆栈时,如果它表示值,它默认为类型 Double。所以我有第二个版本:

public void castPeekOnStack(Type targetType){
    switch (targetType.getSort()) {
        // 不确定
        case Type.BOOLEAN:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
            break;
        case Type.BYTE:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "byteValue", "()B", false);
            break;
        case Type.CHAR:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
            break;
        case Type.SHORT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "shortValue", "()S", false);
            break;
        case Type.INT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "intValue", "()I", false);
            break;
        case Type.FLOAT:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "floatValue", "()F", false);
            break;
        case Type.LONG:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "longValue", "()J", false);
            break;
        case Type.DOUBLE:
            mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
            break;
        case Type.ARRAY:
            mv.visit

<details>
<summary>英文:</summary>

What I want to do is to modify a method using ASM: 
1. I push an object (of class Object) to the stack
2. I want to cast that object to the return type of that method
3. Return that casted object.

My code in the methodVisitor adapter:
```java
    public void visitCode() {
        mv.visitCode();
        if (needModify){
            // package all the method arguments to an Object array and push to the stack
            ...
            // selfReturnTypeDotClassName is the dot class name of return type
            mv.visitLdcInsn(selfReturnTypeDotClassName);
            // push the object (of class Object)
            mv.visitMethodInsn(INVOKESTATIC, MyClass, &quot;getOutputObj&quot;,
                    &quot;([Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;&quot;, false);

            // cast the object to the return type
            castPeekOnStack(selfReturnType);

            mv.visitInsn(selfReturnType.getOpcode(IRETURN));
        }
    }

The method getOutputObj in MyClass (It tries to recover previously recorded Json String to an object using Gson):

public static Object getOutputObj(Object[] args, String methodId, String returnTypeDotClassName){
        HashMap&lt;String, String&gt; inOutMap = getInOutMapOfMethod(methodId);
        // `GSON` is an instance of class `Gson`
        String inputJson = GSON.toJson(args);
        String outputJson = inOutMap.get(inputJson);
        return recoverObjFromJson(outputJson, returnTypeDotClassName);
    }

public static Object recoverObjFromJson(String outputJson, String returnTypeDotClassName){
        try{
            // the object is previously packaged as an object array with length 1.
            Object obj = GSON.fromJson(outputJson, Object[].class)[0];
            return obj;
        }catch (Exception e){
            e.printStackTrace();
            MyEkstaziAgent.log(String.format(&quot;Gson Error: fromJson failed for arguments %s, %s&quot;,
                    outputJson, returnTypeDotClassName));
            return null;
        }
    }

My first version of method castPeekOnStack:

    public void castPeekOnStack(Type targetType){
        switch (targetType.getSort()) {
            // not sure
            case Type.BOOLEAN:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Boolean&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Boolean&quot;, &quot;booleanValue&quot;, &quot;()Z&quot;, false);
                break;
            case Type.BYTE:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Byte&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Byte&quot;, &quot;byteValue&quot;, &quot;()B&quot;, false);
                break;
            case Type.CHAR:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Character&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Character&quot;, &quot;charValue&quot;, &quot;()C&quot;, false);
                break;
            case Type.SHORT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Short&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Short&quot;, &quot;shortValue&quot;, &quot;()S&quot;, false);
                break;
            case Type.INT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Integer&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Integer&quot;, &quot;intValue&quot;, &quot;()I&quot;, false);
                break;
            case Type.FLOAT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Float&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Float&quot;, &quot;floatValue&quot;, &quot;()F&quot;, false);
                break;
            case Type.LONG:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Long&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Long&quot;, &quot;longValue&quot;, &quot;()J&quot;, false);
                break;
            case Type.DOUBLE:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;doubleValue&quot;, &quot;()D&quot;, false);
                break;
            case Type.ARRAY:
                mv.visitTypeInsn(CHECKCAST, targetType.getDescriptor());
                break;
            case Type.OBJECT:
                mv.visitTypeInsn(CHECKCAST, targetType.getInternalName());
                break;
        }
    }

I tried this code on a benchmark whose methods only have int return type. Then I get java.lang.ClassCastException: java.lang.Double cannot be cast to java.lang.Integer. I think when I push the object to the stack, it is of type Double by default if it represents value. So I have the second version:

    public void castPeekOnStack(Type targetType){
        switch (targetType.getSort()) {
            // not sure
            case Type.BOOLEAN:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Boolean&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Boolean&quot;, &quot;booleanValue&quot;, &quot;()Z&quot;, false);
                break;
            case Type.BYTE:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Byte&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;byteValue&quot;, &quot;()B&quot;, false);
                break;
            case Type.CHAR:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Character&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Character&quot;, &quot;charValue&quot;, &quot;()C&quot;, false);
                break;
            case Type.SHORT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Short&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;shortValue&quot;, &quot;()S&quot;, false);
                break;
            case Type.INT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Integer&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;intValue&quot;, &quot;()I&quot;, false);
                break;
            case Type.FLOAT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Float&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;floatValue&quot;, &quot;()F&quot;, false);
                break;
            case Type.LONG:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Long&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;longValue&quot;, &quot;()J&quot;, false);
                break;
            case Type.DOUBLE:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;doubleValue&quot;, &quot;()D&quot;, false);
                break;
            case Type.ARRAY:
                mv.visitTypeInsn(CHECKCAST, targetType.getDescriptor());
                break;
            case Type.OBJECT:
                mv.visitTypeInsn(CHECKCAST, targetType.getInternalName());
                break;
        }
    }

However, I got java.lang.VerifyError: (class: com/D, method: p signature: ()I) Incompatible object argument for function call. I am stuck here, I have no idea why this error is thrown.

答案1

得分: 1

以下是翻译好的部分:

问题似乎是:我在一个不是java/lang/Double类的对象上使用了mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "intValue", "()I", false);。我需要先进行checkcast java/lang/Double操作。我使用了castPeekOnStack方法的第三个版本,错误已经消失:

    public void castPeekOnStack(Type targetType){
        switch (targetType.getSort()) {
            // 不确定的情况
            case Type.BOOLEAN:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
                break;
            case Type.BYTE:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "byteValue", "()B", false);
                break;
            case Type.CHAR:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
                break;
            case Type.SHORT:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "shortValue", "()S", false);
                break;
            case Type.INT:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "intValue", "()I", false);
                break;
            case Type.FLOAT:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "floatValue", "()F", false);
                break;
            case Type.LONG:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "longValue", "()J", false);
                break;
            case Type.DOUBLE:
                mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
                break;
            case Type.ARRAY:
                mv.visitTypeInsn(CHECKCAST, targetType.getDescriptor());
                break;
            case Type.OBJECT:
                mv.visitTypeInsn(CHECKCAST, targetType.getInternalName());
                break;
        }
    }

然而,我还没有在广泛的情况下测试这个方法,我不确定它是否适用于其他返回类型。

上述解决方案只能处理对象为值的情况。当我尝试对引用类型进行强制转换时,它会抛出类似于com.google.gson.internal.LinkedTreeMap cannot be cast to ...的异常。因此,我从Json中恢复对象的方式可能存在问题。

因此,在recoverObjFromJson方法中,我直接将Json强制转换为我想要的类型。需要注意的是,虽然通过fromJson方法将对象转换为我指定的类型,但recoverObjFromJson方法的返回类型仍然是Object,因此我仍然需要在堆栈上进行强制转换。

    public static Object recoverObjFromJson(String outputJson, String returnTypeDotClassName){
        try{
            Object obj = GSON.fromJson(outputJson, getClassObjByName(returnTypeDotClassName));
            return obj;
        }catch (Exception e){
            e.printStackTrace();
            MyEkstaziAgent.log(String.format("Gson Error: fromJson failed for arguments %s, %s",
                    outputJson, returnTypeDotClassName));
            return null;
        }
    }

最后,这个recoverObjFromJson方法与castPeekOnStack的第一个版本配合得很好。

英文:

It seems the problem is: I use mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;intValue&quot;, &quot;()I&quot;, false); on an object which is not of class java/lang/Double. I need to checkcast java/lang/Double first. I used the third version of method castPeekOnStack, the error has gone:

    public void castPeekOnStack(Type targetType){
        switch (targetType.getSort()) {
            // not sure
            case Type.BOOLEAN:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Boolean&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Boolean&quot;, &quot;booleanValue&quot;, &quot;()Z&quot;, false);
                break;
            case Type.BYTE:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;byteValue&quot;, &quot;()B&quot;, false);
                break;
            case Type.CHAR:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Character&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Character&quot;, &quot;charValue&quot;, &quot;()C&quot;, false);
                break;
            case Type.SHORT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;shortValue&quot;, &quot;()S&quot;, false);
                break;
            case Type.INT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;intValue&quot;, &quot;()I&quot;, false);
                break;
            case Type.FLOAT:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;floatValue&quot;, &quot;()F&quot;, false);
                break;
            case Type.LONG:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;longValue&quot;, &quot;()J&quot;, false);
                break;
            case Type.DOUBLE:
                mv.visitTypeInsn(CHECKCAST, &quot;java/lang/Double&quot;);
                mv.visitMethodInsn(INVOKEVIRTUAL, &quot;java/lang/Double&quot;, &quot;doubleValue&quot;, &quot;()D&quot;, false);
                break;
            case Type.ARRAY:
                mv.visitTypeInsn(CHECKCAST, targetType.getDescriptor());
                break;
            case Type.OBJECT:
                mv.visitTypeInsn(CHECKCAST, targetType.getInternalName());
                break;
        }
    }

However I haven't test the method on a wide range of cases, I am not sure if it can work for other return types.


The solution above can only handle cases that the object is a value. When I try to cast a reference type, It throws something like com.google.gson.internal.LinkedTreeMap cannot be cast to .... So the way I recover object from Json must have some problems.

So in method recoverObjFromJson, I directly cast the Json to the type I want. It should be noted that, although by fromJson the object is casted to the type I designate, the return type of method recoverObjFromJson is still Object, so I still need to cast it on the stack.

    public static Object recoverObjFromJson(String outputJson, String returnTypeDotClassName){
        try{
            Object obj = GSON.fromJson(outputJson, getClassObjByName(returnTypeDotClassName));
            return obj;
        }catch (Exception e){
            e.printStackTrace();
            MyEkstaziAgent.log(String.format(&quot;Gson Error: fromJson failed for arguments %s, %s&quot;,
                    outputJson, returnTypeDotClassName));
            return null;
        }
    }

Finally, this recoverObjFromJson works well with the first version of castPeekOnStack.

huangapple
  • 本文由 发表于 2020年7月22日 01:51:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/63020268.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定