Makarov Alexey
Makarov Alexey

Reputation: 41

How to collect information about bytecode instructions before 'getfield' and 'putfield'?

I work with the byte-buddy-maven plugin. I need to walk through constructors and method code and collect information about bytecode instruction before 'getfield' and 'putfield' instructions. How I can do this? I know about MemberSubstitution, but I need to call static methods of utility class with the field name. Here is my code:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.mycompany.mylib;

import java.io.IOException;
import java.lang.reflect.Method;
import net.bytebuddy.asm.MemberSubstitution;
import net.bytebuddy.build.BuildLogger;
import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.TypeVariableSource;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeDescription.Generic.Visitor;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.constant.JavaConstantValue;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.JavaConstant;

/**
 *
 * @author makarov
 */
public class ByteBuddyPlugin implements Plugin {

    private final BuildLogger logger;

    public ByteBuddyPlugin(BuildLogger logger) {
        this.logger = logger;
    }

    @Override
    public boolean matches(TypeDescription target) {
        return target.getDeclaredAnnotations()
                .isAnnotationPresent(Entity.class);
    }

    @Override
    public DynamicType.Builder<?> apply(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
        try {
            Method setValueMethod = ObjectDB.class.getMethod("setValue", Object.class, String.class, Object.class); // args, first - value from aload_0, second - field name, third - value (in byte byte some instruction)
            Method getValueMethod = ObjectDB.class.getMethod("getValue", Object.class, String.class); // args: 1 - value from aload_0, 2 - field name
            
            logger.info(String.format("applying for %s", typeDescription.getSimpleName()));
            return builder.visit(
                    MemberSubstitution.strict()
                            .field(ElementMatchers.declaresAnnotation(ElementMatchers.annotationType(Field.class)))
                            .onWrite()
                            .replaceWith(new MemberSubstitution.Substitution.Factory() {
                                
                                @Override
                                public MemberSubstitution.Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) {
                                    logger.info(String.format("substitution writer in method %s", instrumentedMethod));
                                    
                                    return new MemberSubstitution.Substitution() {
                                        
                                        @Override
                                        public StackManipulation resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset) {
                                            logger.info(String.format("resolve targetType %s, target %s, parameters %s, result %s, freeOffset %s", targetType, target, parameters, result, freeOffset));
                                            
                                            String fieldName = target.getDeclaredAnnotations()
                                                    .ofType(Field.class)
                                                    .load()
                                                    .value();
                                            
                                            logger.info(String.format("fieldName: %s", fieldName));
                                            
                                            // Here is my question... I need to get existing instruction that pushes assigning value to stack for 'putfield' (third argument of 'setValueMethod')

                                            return new StackManipulation.Compound(
                                                    new JavaConstantValue(JavaConstant.Simple.ofLoaded(fieldName)),
                                                    ... //  existing instruction,
                                                    MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(setValueMethod))
                                            );
                                        }
                                    };
                                }
                            }).on(ElementMatchers.any())
            );
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(ex);
        }
    }

    @Override
    public void close() throws IOException {
        logger.info(String.format("closing"));
        /* do nothing */
    }
}

Upvotes: 1

Views: 189

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44007

If you need to act based on specific byte code instructions, Byte Buddy exposes ASM for this exact purpose. The MemberSubstitution is rather meant for situations where you want to "replace calls to method A with calls to method B" or "instead of reading field C, call method D".

Byte Buddy is stateless, ASM is stateful. In your case, you want to make a change based on a previous state and ASM is the choice for this. You can still use Byte Buddy for the Maven plugin and method matching, but use a MethodVisitor which you can register via a AsmVisitorWrapper.ForDeclaredMethods.

Upvotes: 1

Related Questions