【问题标题】:How to dynamically cast an object (of class Object) to the method return type using ASM?如何使用 ASM 将对象(类 Object)动态转换为方法返回类型?
【发布时间】:2020-07-21 17:51:42
【问题描述】:

我想做的是使用 ASM 修改方法:

  1. 我将一个对象(属于 Object 类)推入堆栈
  2. 我想将该对象转换为该方法的返回类型
  3. 返回该投射对象。

methodVisitor 适配器中我的代码:

    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, "getOutputObj",
                    "([Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object;", false);

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

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

MyClass 中的 getOutputObj 方法(它尝试使用 Gson 将先前记录的 Json String 恢复到对象):

public static Object getOutputObj(Object[] args, String methodId, String returnTypeDotClassName){
        HashMap<String, String> 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("Gson Error: fromJson failed for arguments %s, %s",
                    outputJson, returnTypeDotClassName));
            return null;
        }
    }

我的第一个版本的方法castPeekOnStack

    public void castPeekOnStack(Type targetType){
        switch (targetType.getSort()) {
            // not sure
            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()) {
            // not sure
            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.visitTypeInsn(CHECKCAST, targetType.getDescriptor());
                break;
            case Type.OBJECT:
                mv.visitTypeInsn(CHECKCAST, targetType.getInternalName());
                break;
        }
    }

但是,我收到了java.lang.VerifyError: (class: com/D, method: p signature: ()I) Incompatible object argument for function call。我被困在这里,我不知道为什么会抛出这个错误。

【问题讨论】:

  • 你为什么评论checkcastDouble.intValue 不能在 Object 上调用。
  • @apangin 我试图取消注释所有checkcast,它报告相同的错误消息。
  • @apangin 你是对的。我需要首先checkcast java/lang/Double 输入INTFLOATSHORTLONGBYTE

标签: java jvm gson instrumentation java-bytecode-asm


【解决方案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()) {
            // not sure
            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 的第一个版本配合使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多