ByteBuddy: AbstractMethodError when set interceptor - byte-buddy

The following is my learning code, when I executing the code, an exception happened:
Exception in thread "main"
java.lang.AbstractMethodError: org.learning.UserRepository$ByteBuddy$etz0xUhc.$$_pharos_set_interceptor(Lorg/learning/Interceptor;)V**
ByteBuddy version: 1.10.14
final TypeCache.SimpleKey cacheKey = getCacheKey(learningClazz, interceptor.getClass());
Class proxyClass = load(learningClazz, proxyCache, cacheKey, byteBuddy ->
byteBuddy
.subclass(learningClazz)
.defineField(ProxyConfiguration.INTERCEPTOR_FIELD_NAME, Interceptor.class, Visibility.PRIVATE)
.method(not(isDeclaredBy(Object.class)))
.intercept(MethodDelegation.to(ProxyConfiguration.InterceptorDispatcher.class))
.implement(ProxyConfiguration.class)
.intercept(FieldAccessor.ofField(ProxyConfiguration.INTERCEPTOR_FIELD_NAME)
.withAssigner(Assigner.DEFAULT, Assigner.Typing.DYNAMIC)));
final ProxyConfiguration proxy = (ProxyConfiguration) proxyClass.getDeclaredConstructor().newInstance();
proxy.$$_pharos_set_interceptor(interceptor);
return (T) proxy;
public interface ProxyConfiguration {
String INTERCEPTOR_FIELD_NAME = "$$_pharos_interceptor";
void $$_pharos_set_interceptor(Interceptor interceptor);
class InterceptorDispatcher {
#RuntimeType
public static Object intercept(
#This final Object instance,
#Origin final Method method,
#AllArguments final Object[] arguments,
#StubValue final Object stubValue,
#FieldValue(INTERCEPTOR_FIELD_NAME) Interceptor interceptor,
#SuperMethod Method superMethod
) throws Throwable
{
if (interceptor == null) {
return stubValue;
}
else {
return interceptor.intercept(instance, method, arguments, superMethod);
}
}
}
}

Package-private methods are overriden but the JVM will not dispatch them dynamically if the subclass is loaded on a different class loader. If you declare the method public, the problem should be solved. Alternatively, inject the class into the target class loader.

Related

Unable to add mutator for an existing field of a class

I'm trying to add a mutator for an existing private final field. I can transform the field modifiers to remove the final specification and add an accessor method:
// accessor interface
public interface UniqueIdAccessor {
Serializable getUniqueId();
}
// mutator interface
public interface UniqueIdMutator {
void setUniqueId(Serializable uniqueId);
}
...
// fragment of Java agent implementation
return new AgentBuilder.Default()
.type(hasSuperType(named("org.junit.runner.Description")))
.transform(new Transformer() {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
return builder.field(named("fUniqueId")).transform(ForField.withModifiers(FieldManifestation.PLAIN))
.implement(UniqueIdAccessor.class).intercept(FieldAccessor.ofField("fUniqueId"))
// .implement(UniqueIdMutator.class).intercept(FieldAccessor.ofField("fUniqueId"))
.implement(Hooked.class);
}
})
.installOn(instrumentation);
...
Here's a method that uses reflection to check the modifiers of the target field and calls the accessor to get the value of the field.
private static void injectProxy(Description description) {
try {
Field bar = Description.class.getDeclaredField("fUniqueId");
System.out.println("isFinal: " + ((bar.getModifiers() & Modifier.FINAL) != 0));
} catch (NoSuchFieldException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Serializable uniqueId = ((UniqueIdAccessor) description).getUniqueId();
System.out.println("uniqueId: " + uniqueId);
}
// isFinal: false
// uniqueId: <description-unique-id>
... but if I uncomment the second "implement" expression to add the mutator, the transform blows up:
// isFinal: true
// java.lang.ClassCastException:
// class org.junit.runner.Description cannot be cast to class com.nordstrom.automation.junit.UniqueIdAccessor
// (org.junit.runner.Description and com.nordstrom.automation.junit.UniqueIdAccessor
// are in unnamed module of loader 'app')
I could set the field value with reflection, but that defeats the purpose of using Byte Buddy in the first place!
The problem with this approach is that the field accessor considers the input type prior to the modification. Byte Buddy prohibits this as it does not consider the mutation to be legal, not knowing about the removed modifier. As a result, the transformation fails in its entirety and you get the error you are seeing. (Register a listener to see this error.)
To avoid this, you can implement a custom Implementation using FieldAccess (without or). You can have a look at the more convenient FieldAccessor to see how this is implemented, only that you need to drop the validity checks.
Thanks for pointing me in the right direction! I assemble the StackManipulation object that defines the mutator method with this:
final TypeDescription description = TypePool.Default.ofSystemLoader().describe("org.junit.runner.Description").resolve();
final Generic _void_ = TypeDescription.VOID.asGenericType();
final Generic serializable = TypePool.Default.ofSystemLoader().describe("java.io.Serializable").resolve().asGenericType();
final MethodDescription.Token setUniqueIdToken = new MethodDescription.Token("setUniqueId", Modifier.PUBLIC, _void_, Arrays.asList(serializable));
final MethodDescription setUniqueId = new MethodDescription.Latent(description, setUniqueIdToken);
final Token fUniqueIdToken = new FieldDescription.Token("fUniqueId", Modifier.PRIVATE, serializable);
final FieldDescription fUniqueId = new FieldDescription.Latent(description, fUniqueIdToken);
final StackManipulation setUniqueIdImpl = new StackManipulation.Compound(
MethodVariableAccess.loadThis(),
MethodVariableAccess.load(setUniqueId.getParameters().get(0)),
Assigner.DEFAULT.assign(serializable, serializable, Typing.STATIC),
FieldAccess.forField(fUniqueId).write(),
MethodReturn.VOID
);
... and I transform the target class with this:
return new AgentBuilder.Default()
.type(hasSuperType(named("org.junit.runner.Description")))
.transform(new Transformer() {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
return builder.field(named("fUniqueId")).transform(ForField.withModifiers(FieldManifestation.PLAIN))
.implement(AnnotationsAccessor.class).intercept(FieldAccessor.ofField("fAnnotations"))
.implement(UniqueIdAccessor.class).intercept(FieldAccessor.ofField("fUniqueId"))
.implement(UniqueIdMutator.class).intercept(new Implementation.Simple(setUniqueIdImpl));
}
})
.installOn(instrumentation);
Here are the definitions of the three interfaces used in the transform:
// annotations accessor interface
public interface AnnotationsAccessor {
Annotation[] annotations();
}
// unique ID accessor interface
public interface UniqueIdAccessor {
Serializable getUniqueId();
}
// unique ID mutator interface
public interface UniqueIdMutator {
void setUniqueId(Serializable uniqueId);
}

Around annotion executed twice using WebFlux

I'm facing a weird behaviour while using AOP with AspectJ.
Basically the #Around method its called either once either twice and while trying to debugging I can't find the reason why it's being executing twice (I mean what triggers the second execution of the method)
here is some code :
#Aspect
#Slf4j
public class ReactiveRedisCacheAspect {
#Pointcut("#annotation(com.xxx.xxx.cache.aop.annotations.ReactiveRedisCacheable)")
public void cacheablePointCut() {}
#Around("cacheablePointCut()")
public Object cacheableAround(final ProceedingJoinPoint proceedingJoinPoint) {
log.debug("ReactiveRedisCacheAspect cacheableAround.... - {}", proceedingJoinPoint);
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
Method method = methodSignature.getMethod();
Class<?> returnTypeName = method.getReturnType();
Duration duration = Duration.ofHours(getDuration(method));
String redisKey = getKey(method, proceedingJoinPoint);
if (returnTypeName.isAssignableFrom(Flux.class)) {
log.debug("returning Flux");
return cacheRepository.hasKey(redisKey)
.filter(found -> found)
.flatMapMany(found -> cacheRepository.findByKey(redisKey))
.flatMap(found -> saveFlux(proceedingJoinPoint, redisKey, duration));
} else if (returnTypeName.isAssignableFrom(Mono.class)) {
log.debug("Returning Mono");
return cacheRepository.hasKey(redisKey)
.flatMap(found -> {
if (found) {
return cacheRepository.findByKey(redisKey);
} else {
return saveMono(proceedingJoinPoint, redisKey, duration);
}
});
} else {
throw new RuntimeException("non reactive object supported (Mono,Flux)");
}
}
private String getKey(final Method method, final ProceedingJoinPoint proceedingJoinPoint) {
ReactiveRedisCacheable annotation = method.getAnnotation(ReactiveRedisCacheable.class);
String cacheName = annotation.cacheName();
String key = annotation.key();
cacheName = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, cacheName);
key = (String) AspectSupportUtils.getKeyValue(proceedingJoinPoint, key);
return cacheName + "_" + key;
}
}
public class AspectSupportUtils {
private static final ExpressionEvaluator evaluator = new ExpressionEvaluator();
public static Object getKeyValue(JoinPoint joinPoint, String keyExpression) {
if (keyExpression.contains("#") || keyExpression.contains("'")) {
return getKeyValue(joinPoint.getTarget(), joinPoint.getArgs(), joinPoint.getTarget().getClass(),
((MethodSignature) joinPoint.getSignature()).getMethod(), keyExpression);
}
return keyExpression;
}
private static Object getKeyValue(Object object, Object[] args, Class<?> clazz, Method method, String keyExpression) {
if (StringUtils.hasText(keyExpression)) {
EvaluationContext evaluationContext = evaluator.createEvaluationContext(object, clazz, method, args);
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, clazz);
return evaluator.key(keyExpression, methodKey, evaluationContext);
}
return SimpleKeyGenerator.generateKey(args);
}
}
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ReactiveRedisCacheable {
String key();
String cacheName();
long duration() default 1L;
}
#RestController
#RequestMapping("api/pub/v1")
public class TestRestController{
#ReactiveRedisCacheable(cacheName = "test-cache", key = "#name", duration = 1L)
#GetMapping(value = "test")
public Mono<String> getName(#RequestParam(value = "name") String name){
return Mono.just(name);
}
}
#Configuration
public class Config {
#Bean
public ReactiveRedisCacheAspect reactiveRedisCache (ReactiveRedisCacheAspect reactiveRedisCacheAspect) {
return reactiveRedisCacheAspect;
}
}
logs:
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.096 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.097 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ctor-http-nio-3] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
ReactiveRedisCacheAspect cacheableAround.... - {}execution(Mono com.abc.def.xxx.rest.TestRestcontroller.getName(String))
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.a.i.ReactiveRedisCacheAspect : Returning Mono
2021-06-04 15:36:23.236 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : searching key: (bff_pippo)
2021-06-04 15:36:23.250 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
2021-06-04 15:36:23.275 INFO [fo-bff,f688025287be7e7c,f688025287be7e7c] 20060 --- [ioEventLoop-7-2] c.m.s.c.repository.CacheRepositoryImpl : saving obj: (key:bff_pippo) (expiresIn:3600s)
So far I would have expected the cacheableAround would be executed only once, but what happens its a bit weird, if the object is present on redis the method is executed only once but if is not present the method is executed twice which it doesn't make sense, moreover it should be the business logic to manage what to do inside the method.
Thanks in advance!
You did not mention whether you use native AspectJ via load- or compile-time weaving or simply Spring AOP. Because I see not #Component annotation on your aspect, it might as well be native AspectJ, unless you configure your beans via #Bean factory methods in a configuration class or XML.
Assuming that you are using full AspectJ, a common problem newbies coming from Spring AOP have, is that they are not used to the fact that AspectJ not only intercepts execution joinpoints, but also call ones. This leads to the superficial perception that the same joinpoint is intercepted twice. But in reality, it is once the method call (in the class from which the call is made) and once the method execution (in the class where the target method resides). This is easy to determine if at the beginning of your advice method you simply log the joinpoint. In your case:
System.out.println(proceedingJoinPoint);
If then on the console you see something like
call(public void org.acme.MyClass.myMethod())
execution(public void org.acme.MyClass.myMethod())
then you know what is happening.
In case you use Spring AOP, probably it is an issue with the aspect or the Redis caching behaviour that is different from your expectation.

ByteBuddy intercepting constructor arguments

I am trying to dynamically create a class using ByteBuddy with my custom constructor.
I have read the Intercepting default constructor with Byte Buddy and I have written the following code base on that.
Class<?> dynamicType = new ByteBuddy().subclass(Object.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name("foo").defineConstructor(Modifier.PUBLIC).withParameters(int.class)
.intercept(
to(new Object() {
public void construct() throws Exception {
System.out.println("before constructor");
}
})
.andThen(MethodCall.invoke(Object.class.getConstructor()))
.andThen(to(new Object() {
public void construct() throws Exception {
System.out.println("after constructor");
}})
))
.make()
.load(Main.class.getClassLoader(), INJECTION)
.getLoaded();
dynamicType.getConstructor(int.class).newInstance(3);
My question is how can I access the integer argument of 'foo' constructor in the custom codes that I have added before and after calling the super constructor.
Sure, simply define a parameter with an annotation #Argument(0).
I'd recommend against using anonymous classes as their package-private visibility might render tricky outcomes.

Syntax error when adding Runnable in static initializer with javassist

I want to use javassist (version 3.19-GA) to generate bytecode of a static initializer of a class that starts a thread. For some reason I cannot understand javassist expects a ";" somewhere even though I believe the code I provide is syntactically correct. Does someone see more than I do? Here is the code. What is the problem?
ClassPool pool = ClassPool.getDefault();
final CtClass clazz = pool.get(somename);
clazz.makeClassInitializer().insertAfter(
"try{Runnable r=new Runnable () {public void run () { System.out.println (\"hello!!!!\"); }}; " +
"new Thread(r).start(); } catch(Exception e){}");
I'm getting the following exception:
javassist.CannotCompileException: [source error] ; is missing
at javassist.CtBehavior.insertAfter(CtBehavior.java:877)
at javassist.CtBehavior.insertAfter(CtBehavior.java:792)
at my.code(myclass.java:111)
Thanks for any hint.
Most probably javassist compiler does not support anonymous inner classes like new Runnable () {...}
You have to create new class, inherit it from Runnable, implement method run and create object of this class in your constructor.
package hello;
import javassist.*;
class Test{
}
class Main {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
final CtClass clazz = pool.get(Test.class.getCanonicalName());
CtClass runnable = pool.makeClass("my.custom.RunnableImpl");
runnable.addInterface(pool.get("java.lang.Runnable"));
CtMethod method = CtNewMethod.make("public void run() { System.out.println(\"hello!!!!\"); }", runnable);
runnable.addMethod(method);
// load class
runnable.toClass();
clazz.setName("newTest");
CtConstructor ctConstructor = clazz.makeClassInitializer();
ctConstructor.insertAfter("try{ new Thread( new my.custom.RunnableImpl() ).start(); } catch(Exception e){}");
Class aClass = clazz.toClass();
// call initializer
Class.forName(aClass.getCanonicalName());
Thread.sleep(1000);
}
}
From the official documentation of void insertAfter(String src):
Parameters:
src - the source code representing the inserted bytecode. It must be a
single statement or block.
In your src String parameter, you don't provide a single statement or a block.
A block is "{}".
Try insertBefore(String src) method with global enclosing "{}":
ClassPool pool = ClassPool.getDefault();
final CtClass clazz = pool.get(somename);
clazz.makeClassInitializer().insertBefore(
"{try{Runnable r = new Runnable () {public void run () { System.out.println (\"hello!!!!\"); }}; " +
"new Thread(r).start(); } catch(Exception e){}}");

How to mock out InetAddress.getLocalHost() using JMockit

The InetAddress constructor is not visible because the factory pattern is used.
final InetAddress anyInstance = InetAddress.getLocalHost();
new NonStrictExpectations(InetAddress.class) {
{
anyInstance.getHostAddress();
result = "192.168.0.101";
}
};
When I try to use the factory method to get an instance for partial mocking I get the error:
java.lang.IllegalStateException: Missing invocation to mocked type at this point; please make sure such invocations appear only after the declaration of a suitable mock field or parameter
You need to specify that InetAddress and any subclasses should be mocked:
#Test
public void mockAnyInetAddress(#Capturing final InetAddress anyInstance)
throws Exception
{
new Expectations() {{
anyInstance.getHostAddress(); result = "192.168.0.101";
}};
String localHostAddress = InetAddress.getLocalHost().getHostAddress();
assertEquals("192.168.0.101", localHostAddress);
}