How to transform bytecodes to initialize primitive constants in static block with ASM?

谁说我不能喝 提交于 2021-02-08 09:56:51

问题


I need to transform Java bytecode with ASM to initialize public static final fields inside a static {...} block in the class. For example:

Input:

public static final int CONSTANT = 10;

Output:

public static final int CONSTANT;

static {
    CONSTANT = 10;
}

I need this transformation because the compiler replaces primitive constants by their actual value in the bytecode, so their usage becomes untraceable. This transformation allows tracing the usage of constants.


回答1:


For such a transformation, you can use the usual ClassReaderClassVisitor(transformer) →ClassWriter chain. There are three fundamental steps:

  • Override visitField to track all fields with a constant value and call the super visit method without a constant, i.e. with null, to keep the field declaration but remove the constant value.

  • Override visitMethod to notice whether there’s an already existing class initializer (<clinit> method). If so, return a special MethodVisitor which will inject the field initialization at the beginning of the code and clear the map so the third step becomes a no-op.

  • Override visitEnd to create a class initializer if there were constant fields and no existing class initializer. The newly created class initializer has to do the same field assignments, so it’s worth having the common code in an injectFieldInit method. Then, we only have to append the mandatory RETURN instruction which we don’t need to add for an already existing initializer.

This code uses an array as map key, which is no problem here, as each field is distinct, so the fact that arrays have no content based equals method is irrelevant. We could have used a List<Map.Entry<…>> instead or a list of a dedicated element type for holding all necessary values, with the same result as the code does no lookup but only iterates over the discovered fields once.

public static byte[] transform(byte[] classFile) {
    ClassReader cr = new ClassReader(classFile);
    ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
    ClassVisitor trans = new ClassVisitor(Opcodes.ASM5, cw) {
        private String currClassName;
        private Map<String[],Object> constants = new HashMap<>();
        @Override public void visit(int version, int acc, String name,
                                    String sig, String superName, String[] ifs) {
            currClassName = name;
            super.visit(version, acc, name, sig, superName, ifs);
        }
        @Override public FieldVisitor visitField(int acc, String name, String desc,
                                                 String sig, Object value) {
            if(value != null && (acc & Opcodes.ACC_STATIC) != 0)
                constants.put(new String[]{currClassName, name, desc}, value);
            return super.visitField(acc, name, desc, sig, null);
        }
        @Override public MethodVisitor visitMethod(int acc, String name, String desc,
                                                   String sig, String[] ex) {
            MethodVisitor mv = super.visitMethod(acc, name, desc, sig, ex);
            if(name.equals("<clinit>")) {
                mv = new MethodVisitor(Opcodes.ASM5, mv) {
                    @Override public void visitCode() {
                        super.visitCode();
                        injectFieldInit(this, constants);
                        constants.clear();
                    }
                };
            }
            return mv;
        }
        @Override public void visitEnd() {
            if(!constants.isEmpty()) {
                MethodVisitor mv = super.visitMethod(
                    Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
                mv.visitCode();
                injectFieldInit(mv, constants);
                mv.visitInsn(Opcodes.RETURN);
                mv.visitMaxs(-1, -1);
                mv.visitEnd();
            }
            super.visitEnd();
        }
    };
    cr.accept(trans, 0);
    return cw.toByteArray();
}
static void injectFieldInit(MethodVisitor target, Map<String[], Object> constants) {
    for(Map.Entry<String[],Object> e: constants.entrySet()) {
        target.visitLdcInsn(e.getValue());
        String[] field = e.getKey();
        target.visitFieldInsn(Opcodes.PUTSTATIC, field[0], field[1], field[2]);
    }
}


来源:https://stackoverflow.com/questions/61167940/how-to-transform-bytecodes-to-initialize-primitive-constants-in-static-block-wit

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!