问题描述:

I want to transform a field of a class to be effectively a constant. I'm using ASM 5.0.3.

Here is the test class I have:

public class BytecodeUtilsTest {

@BeforeClass

public static void beforeClass(){

String replaceFieldClassName = "com.mypackage.ClassWithFieldToReplaceWithConstant";

String replaceFieldClassNameAsPath = replaceFieldClassName.replace('.', '/') + ".class";

// standard code to redefine class (inspired by ASM FAQ http://asm.ow2.org/doc/faq.html, sec. 5)

ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();

InputStream stream = contextClassLoader.getResourceAsStream(replaceFieldClassNameAsPath);

byte[] classBytes;

try {

classBytes = IOUtils.toByteArray(stream);

// here is the interesting part

byte[] patchedClassBytes = BytecodeUtils.patch(classBytes, "_fieldToReplace", true);

Reflection.invoke(contextClassLoader, "defineClass", Class.class,

new Class[]{String.class, byte[].class, int.class, int.class},

new Object[]{replaceFieldClassName, patchedClassBytes, 0, patchedClassBytes.length});

} catch (IOException e) {

throw new RuntimeException(e);

}

}

@Test

public void testFieldReplace(){

Assert.assertTrue(new ClassWithFieldToReplaceWithConstant().getFieldToReplace());

Assert.assertTrue(new ClassWithFieldToReplaceWithConstant()._fieldToReplace);

}

}

Here is the test class to update:

public class ClassWithFieldToReplaceWithConstant {

boolean _fieldToReplace;

public boolean getFieldToReplace() {

return _fieldToReplace;

}

}

And here is the patcher:

public class BytecodeUtils {

public static byte[] patch(byte[] bytecode, final String fieldToReplace, final boolean value) {

ClassReader classReader = new ClassReader(bytecode);

final ClassWriter classWriter = new ClassWriter(classReader, 0);

ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM4, classWriter) {

@Override

public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

return new MethodVisitor(Opcodes.ASM4, super.visitMethod(access, name, desc, signature, exceptions)) {

@Override

public void visitFieldInsn(int opcode, String owner, String name, String desc) {

if (opcode == Opcodes.GETFIELD && name.equals(fieldToReplace)) {

mv.visitInsn(Opcodes.POP);

mv.visitInsn(value ? Opcodes.ICONST_1 : Opcodes.ICONST_0);

} else {

super.visitFieldInsn(opcode, owner, name, desc);

}

}

};

}

};

classReader.accept(classVisitor, 0);

return classWriter.toByteArray();

}

}

The problem is test fails at the second assert. So if I use getter, it returns trueas expected, but if I read field directly it returns false. That's quite unexpected considering the fact that getter yields INVOKEVIRTUAL instruction and field read yields GETFIELD which is updated by invocation of visitInsn method.

What am I doing wrong and how to make direct field access return true?

网友答案:

For the second assert to work you need to patch BytecodeUtilsTest, not ClassWithFieldToReplaceWithConstant, since bytecode instruction to read field for the second case is actually in the BytecodeUtilsTest#testFieldReplace method.

Getter case works fine since instruction to read the field is inside the getter body, i.e, inside the ClassWithFieldToReplaceWithConstant class.

If in real scenario field is private, this code should be fine (since there will be no access to the field from outside of the class field is declared in). Otherwise, you will have to patch every class which reads or writes to this field.

相关阅读:
Top