如何使用 ASM 将对象(属于 class 对象)动态转换为方法 return 类型?
How to dynamically cast an object (of class Object) to the method return type using ASM?
我想做的是修改一个使用ASM的方法:
- 我将一个对象(属于 class 个对象)推入堆栈
- 我想将该对象转换为该方法的 return 类型
- Return 铸造的对象。
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字符串恢复为对象):
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
return 类型。然后我得到 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
。我被困在这里,我不知道为什么会抛出这个错误。
问题似乎是:我在一个不属于 class 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;
}
}
不过我还没有在广泛的案例中测试过这个方法,我不确定它是否适用于其他 return 类型。
上面的解决方案只能处理对象是一个值的情况。当我尝试转换引用类型时,它会抛出类似 com.google.gson.internal.LinkedTreeMap cannot be cast to ...
的内容。所以我从Json恢复对象的方式一定有问题。
所以在方法recoverObjFromJson
中,我直接将Json转换为我想要的类型。需要注意的是,虽然通过fromJson
将对象强制转换为我指定的类型,但是方法recoverObjFromJson
的return类型仍然是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
的第一个版本配合得很好。
我想做的是修改一个使用ASM的方法:
- 我将一个对象(属于 class 个对象)推入堆栈
- 我想将该对象转换为该方法的 return 类型
- Return 铸造的对象。
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字符串恢复为对象):
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
return 类型。然后我得到 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
。我被困在这里,我不知道为什么会抛出这个错误。
问题似乎是:我在一个不属于 class 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;
}
}
不过我还没有在广泛的案例中测试过这个方法,我不确定它是否适用于其他 return 类型。
上面的解决方案只能处理对象是一个值的情况。当我尝试转换引用类型时,它会抛出类似 com.google.gson.internal.LinkedTreeMap cannot be cast to ...
的内容。所以我从Json恢复对象的方式一定有问题。
所以在方法recoverObjFromJson
中,我直接将Json转换为我想要的类型。需要注意的是,虽然通过fromJson
将对象强制转换为我指定的类型,但是方法recoverObjFromJson
的return类型仍然是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
的第一个版本配合得很好。