byte-buddy icon indicating copy to clipboard operation
byte-buddy copied to clipboard

ElementMatchers match fail if adding a new field

Open creeew opened this issue 3 years ago • 8 comments

I'm using DynamicType.Builder to enhance class. It's quite weird my MethodDelegation cannot intercet the match method. Like ElementMatchers#match and ElementMatchers#nameContains work incorrectly although the method name match the regex. Everything is alright if just using a methodDelegation to enhance, but add a new field first and then using methodDelegation ElementMatchers cannot match correctly.

you can reproduce by this code

` public static void premain(String agentArgs, Instrumentation instrumentation) { final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(false));

    AgentBuilder agentBuilder = new AgentBuilder
            .Default(byteBuddy)
            .ignore(ElementMatchers.<TypeDescription>nameStartsWith("net.bytebuddy.")
                    .or(ElementMatchers.<TypeDescription>nameStartsWith("org.slf4j."))
                    .or(ElementMatchers.<TypeDescription>nameStartsWith("org.groovy."))
                    .or(ElementMatchers.<TypeDescription>nameContains("javassist"))
                    .or(ElementMatchers.<TypeDescription>nameContains(".asm."))
                    .or(ElementMatchers.<TypeDescription>nameStartsWith(".reflectasm."))
                    .or(ElementMatchers.<TypeDescription>nameStartsWith("sun.reflect"))
                    .or(ElementMatchers.<TypeDescription>isSynthetic()));

    agentBuilder.type(ElementMatchers.<TypeDescription>nameMatches("zq.service.TestService"))
            .transform(new AgentBuilder.Transformer() {
                public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription, ClassLoader classLoader, JavaModule module) {

                    ElementMatcher.Junction<MethodDescription> junction = not(ElementMatchers.<MethodDescription>isStatic()).and(ElementMatchers.<MethodDescription>nameMatches("[a-zA-Z0-9_$]+"));

                    // adding a field first, elementMatcher match fail
                    builder = builder
                            .defineField(CONTEXT_ATTR_NAME, Object.class, Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE)
                            .implement(EnhancedInstance.class)
                            .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));

                    // adding matched method interceptor
                    builder = builder
                            .method(junction)
                            .intercept(MethodDelegation.withDefaultConfiguration().to(new InstMethodsInter()));


                    return builder;
                }
            })
            .installOn(instrumentation);
}`

I've tested it matchs correctly if adding new filed after added a MethodDelegation.

creeew avatar Feb 21 '22 12:02 creeew

Have you tried registering an AgentBuilder.Listener to see if an exception is thrown?

raphw avatar Feb 22 '22 11:02 raphw

Thanks for your advice, I got this "enhance class: zq.service.TestService error: java.lang.IllegalArgumentException: Cannot resolve ambiguous delegation of public abstract java.lang.Object com.zenq.EnhancedInstance.getField() to public java.lang.String java.lang.Object.toString() or public native int java.lang.Object.hashCode() " when registered a Listener. Using ElementerMatchers.nameContain or nameMatch just matched the EnhancedInstance's method which implemented by my enhance class. builder = builder .defineField(CONTEXT_ATTR_NAME, Object.class, Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE) .implement(EnhancedInstance.class) .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME)); Codes working as expect because add methodDelegation before add a new field and methodDelegation will not delegate EnhancedInstance's method. We have to add methodDelegation first if there is no need delegate new filed method? Not sure if I understand correctly. Thank you for your help.

creeew avatar Feb 22 '22 12:02 creeew

It seems like junction captures that field instrumentation. Try switching the two interceptions definitions.

raphw avatar Feb 22 '22 12:02 raphw

It throws "java.lang.IllegalStateException: Cannot set or get value of public java.lang.String zq.service.TestService.test() using private volatile java.lang.Object zq.service.TestService.$enhance_field" when I switching methodDelegation to defineField. Maybe we always using junction capture method first then define new field to avoid junction captures field?

creeew avatar Feb 22 '22 13:02 creeew

Given

.implement(EnhancedInstance.class)
.intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));

Could you show the class EnhancedInstance.

raphw avatar Feb 22 '22 19:02 raphw

here's the class EnhancedInstance

public interface EnhancedInstance {
    Object getField();

    void setField(Object value);
}

parameter CONTEXT_ATTR_NAME value is $enhance_field

Any junction matches the EnhancedInstance's method name will throw java.lang.IllegalArgumentException: Cannot resolve ambiguous delegation of public abstract java.lang.Object com.zenq.EnhancedInstance.getField() to public java.lang.String java.lang.Object.toString() or public native int java.lang.Object.hashCode()

creeew avatar Feb 23 '22 01:02 creeew

I cannot reproduce it if I specify the field bit last:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.objectweb.asm.Opcodes;

public class Bar {

    private static final String CONTEXT_ATTR_NAME = "$sample";

    public static void main(String[] args) throws Exception {
        DynamicType.Builder<?> builder = new ByteBuddy().redefine(Base.class).name("foo.Qux");

        ElementMatcher<MethodDescription> matcher = ElementMatchers
                .not(ElementMatchers.<MethodDescription>isStatic().or(ElementMatchers.<MethodDescription>isDeclaredBy(Object.class)))
                .and(ElementMatchers.<MethodDescription>nameMatches("[a-zA-Z0-9_$]+"));

        builder = builder
                .method(matcher)
                .intercept(MethodDelegation.to(new InstMethodsInter()));

        builder = builder
                .defineField(CONTEXT_ATTR_NAME, Object.class, Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE)
                .implement(EnhancedInstance.class)
                .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));

        Class<?> enhanced = builder.make().load(Base.class.getClassLoader()).getLoaded();

        Object instance = enhanced.getConstructor().newInstance();
        EnhancedInstance enhancedInstance = (EnhancedInstance) instance;

        Object value = new Object();
        enhancedInstance.setField(value);
        System.out.println(enhancedInstance.getField() == value);

    }

    public interface EnhancedInstance {

        Object getField();

        void setField(Object value);
    }

    public static class Base { }

    public static class InstMethodsInter {

        public void nothing() { }
    }
}

raphw avatar Feb 24 '22 08:02 raphw

I can still get exception java.lang.IllegalArgumentException: Cannot resolve ambiguous delegation of public abstract java.lang.Object com.zenq.Bar$EnhancedInstance.getField() to public java.lang.String java.lang.Object.toString() or public native int java.lang.Object.hashCode() if add field first like this

    public static void main(String[] args) throws Exception {
        DynamicType.Builder<?> builder = new ByteBuddy().redefine(Base.class).name("foo.Qux");

        ElementMatcher<MethodDescription> matcher = ElementMatchers
                .not(ElementMatchers.<MethodDescription>isStatic().or(ElementMatchers.<MethodDescription>isDeclaredBy(Object.class)))
                .and(ElementMatchers.<MethodDescription>nameMatches("[a-zA-Z0-9_$]+"));

        // add field first
        builder = builder
                .defineField(CONTEXT_ATTR_NAME, Object.class, Opcodes.ACC_PRIVATE | Opcodes.ACC_VOLATILE)
                .implement(EnhancedInstance.class)
                .intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));

        builder = builder
                .method(matcher)
                .intercept(MethodDelegation.to(new InstMethodsInter()));

        Class<?> enhanced = builder.make().load(Base.class.getClassLoader()).getLoaded();

        Object instance = enhanced.getConstructor().newInstance();
        EnhancedInstance enhancedInstance = (EnhancedInstance) instance;

        Object value = new Object();
        enhancedInstance.setField(value);
        System.out.println(enhancedInstance.getField() == value);

    }

Just wondering whether we have to add field last in DynamicType.Builder.

creeew avatar Feb 24 '22 08:02 creeew