What I want to do is to modify a method using ASM:
- I push an object (of class Object) to the stack
- I want to cast that object to the return type of that method
- 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.
Advertisement
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
.