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

Tags: , , , ,



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:

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

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

My first version of method 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;
        }
    }

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

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.

Answer

It seems the problem is: I use mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "intValue", "()I", 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, "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;
        }
    }

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("Gson Error: fromJson failed for arguments %s, %s",
                    outputJson, returnTypeDotClassName));
            return null;
        }
    }

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



Source: stackoverflow