ElementMatchers match fail if adding a new field
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.
Have you tried registering an AgentBuilder.Listener to see if an exception is thrown?
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.
It seems like junction captures that field instrumentation. Try switching the two interceptions definitions.
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?
Given
.implement(EnhancedInstance.class)
.intercept(FieldAccessor.ofField(CONTEXT_ATTR_NAME));
Could you show the class EnhancedInstance.
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()
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() { }
}
}
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.