views:

399

answers:

2

I'm using the ASM Java library to replace some reflection. I generate the body of this method:

void set(Object object, int fieldIndex, Object value);

With this generated method, I can set fields on an object at runtime without using reflection. It works great. However, I found it fails for primitive fields. Here is the relevant part of my set method:

for (int i = 0, n = cachedFields.length; i < n; i++) {
 mv.visitLabel(labels[i]);
 mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
 mv.visitVarInsn(ALOAD, 1);
 mv.visitTypeInsn(CHECKCAST, targetClassName);
 mv.visitVarInsn(ALOAD, 3);
 Field field = cachedFields[i].field;
 Type fieldType = Type.getType(field.getType());
 mv.visitFieldInsn(PUTFIELD, targetClassName, field.getName(), fieldType.getDescriptor());
 mv.visitInsn(RETURN);
}

This code is generating case labels for a select. It works great for objects but for primitives I get this error:

Expecting to find float on stack

Ok, that makes sense, I need to do the unboxing myself. I implemented the following:

for (int i = 0, n = cachedFields.length; i < n; i++) {
 mv.visitLabel(labels[i]);
 mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
 mv.visitVarInsn(ALOAD, 1);
 mv.visitTypeInsn(CHECKCAST, targetClassName);
 mv.visitVarInsn(ALOAD, 3);

 Field field = cachedFields[i].field;
 Type fieldType = Type.getType(field.getType());
 switch (fieldType.getSort()) {
 case Type.BOOLEAN:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
  break;
 case Type.BYTE:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
  break;
 case Type.CHAR:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
  break;
 case Type.SHORT:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
  break;
 case Type.INT:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
  break;
 case Type.FLOAT:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
  break;
 case Type.LONG:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
  break;
 case Type.DOUBLE:
  mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
  mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
  break;
 case Type.ARRAY:
  mv.visitTypeInsn(CHECKCAST, fieldType.getDescriptor());
  break;
 case Type.OBJECT:
  mv.visitTypeInsn(CHECKCAST, fieldType.getInternalName());
  break;
 }

 mv.visitFieldInsn(PUTFIELD, targetClassName, field.getName(), fieldType.getDescriptor());
 mv.visitInsn(RETURN);
}

I have traced through and it definitely goes into "case Type.FLOAT" for the appropriate field, however, I get this error:

Expecting to find object/array on stack

This is where I'm stuck. For the life of me I can't figure out why the unboxing doesn't work. The "ALOAD, 3" is putting the third parameter of the set method on the stack, which should be a Float. Any ideas?

I found the asm-commons library has a GeneratorAdapter class that has an unbox method. However, I don't really want to include yet another JAR for something that should be so simple. I looked at the GeneratorAdapter source and it is doing something very similar. I tried to modify my code to use GeneratorAdapter, just to see if it worked, but didn't find it at all easy to convert.

A: 

Turns out the unboxing above was working properly. I had code that was doing a get and not boxing the result before trying to return it as an Object. My fault for not having a simpler test!

In case someone else needs it, here is the proper code for boxing:

Type fieldType = Type.getType(...);
switch (fieldType.getSort()) {
case Type.BOOLEAN:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
 break;
case Type.BYTE:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
 break;
case Type.CHAR:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
 break;
case Type.SHORT:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
 break;
case Type.INT:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
 break;
case Type.FLOAT:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
 break;
case Type.LONG:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
 break;
case Type.DOUBLE:
 mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
 break;
}
NateS
+1  A: 

Use GeneratorAdapter its must cleaner than MethodVisitor and has an unbox() which iniserts the right invoke primitive.valueOf() method call.

mP