byte-buddy Advice or Intercept Constructor invocation - byte-buddy

I am trying to intercept or advice constructor invocation as following:
new AgentBuilder.Default()
//.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.TypeStrategy.Default.REDEFINE)
.type(ElementMatchers.nameMatches("org.openqa.selenium.firefox.FirefoxDriver"))
.transform((builder, typeDescription, classLoader, module) -> builder
.constructor(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(FirefoxDriverInterceptor.class))))
//.intercept(SuperMethodCall.INSTANCE.andThen(Advice.to(FirefoxDriverConstructorAdvice.class))))
Interceptor:
#RuntimeType
public static void intercept(#Origin Constructor<?> constructor) {
System.out.println("Intercepted: " + constructor.getName());
}
Advice:
#Advice.OnMethodExit
public static void after(//#Advice.This Object thisObject,
#Advice.Origin String method,
#Advice.AllArguments Object[] args
) {
logger.info("<----- instantiated firefoxwebdriver: {}", method);
}
on trying with interceptor or advice, exception throws is:
java.lang.IllegalStateException: Cannot call super (or default) method for public org.openqa.selenium.firefox.FirefoxDriver(org.openqa.selenium.firefox.GeckoDriverService,org.openqa.selenium.Capabilities)
at net.bytebuddy.implementation.SuperMethodCall$Appender.apply(SuperMethodCall.java:102)
at net.bytebuddy.implementation.bytecode.ByteCodeAppender$Compound.apply(ByteCodeAppender.java:134)
Let me know for nay pointers. Thank you for your help.

You are blending Advice and MethodDelegation here which are not related. They have some common concepts, but you cannot mix the annotations.
You can however wrap advice around a delegation:
Advice.to(...).wrap(MethodDelegation.to(...))

Related

NoClassDefFoundError for a private Enum from a helper class used in Advice

i'm trying to intercept a handler method from a class using this agent builder
File temp = Files.createTempDirectory("tmp").toFile();
Map<TypeDescription, byte[]> map = new HashMap<>();
map.put(new TypeDescription.ForLoadedType(LambdaHandlerRuntime.class), ClassFileLocator.ForClassLoader.read(LambdaHandlerRuntime.class));
map.put(new TypeDescription.ForLoadedType(Interface.class), ClassFileLocator.ForClassLoader.read(Interface.class));
map.put(new TypeDescription.ForLoadedType(MyLogger.class), ClassFileLocator.ForClassLoader.read(MyLogger.class));
map.put(new TypeDescription.ForLoadedType(Utils.class), ClassFileLocator.ForClassLoader.read(Utils.class));
ClassInjector.UsingInstrumentation.of(temp, ClassInjector.UsingInstrumentation.Target.BOOTSTRAP, instrumentation).inject(map);
AgentBuilder.Transformer methodsTransformer = new AgentBuilder.Transformer() {
#Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule javaModule) {
return builder.method(ElementMatchers.named("handleRequest"))
.intercept(MethodDelegation.to(LambdaHandlerRuntime.class));
}
};
AgentBuilder.Listener listener = new AgentBuilder.Listener.WithTransformationsOnly(
new AgentBuilder.Listener.StreamWriting(System.out));
new AgentBuilder.Default().type(ElementMatchers.named(handler)).transform(methodsTransformer)
.with(listener).installOn(instrumentation);
i'm injecting the Helper classes needed into bootstrap loader as you can see.
but MyLogger.class has a private enum private enum LogLevel { ERROR, WARNING, INFO, DEBUG }
for which i'm getting NoClassDefFoundError java.lang.NoClassDefFoundError: io/protego/fsp/utils/MyLogger$LogLevel
i guess there is a way to handle enum. any help will be appreciated.
Thanks.
Advice inlines code into target methods. From their inlined location, the code will have to respect the same restrictions as code that would be written at these locations. If you can control the class loaders of these methods, you can refer to code outside of the advice, but you have to make the classes visible. Otherwise, these errors will occur.
From the no class def found error, it does however seem that you instrument a class that cannot see the enum. In this case, consider injecting it into the bootstrap loader (via Instrumentation, for example), and making it public.

ByteBuddy call constructor of generated class

I am trying to generate a method like this in a generated class
static void setDefault(Supplier<?> arg0, String[] arg1, String[] arg2) {
defaultInstance = new GeneratedClass(arg0, arg1, arg2);
}
I am struggling with the API, where I'm currently is
.defineMethod("setDefault", Void.TYPE, Visibility.PACKAGE_PRIVATE, Ownership.STATIC, MethodArguments.PLAIN)
.withParameters(Supplier.class, String[].class, String[].class)
.intercept(....)
I believe I need FieldAccessor.ofField and MethodCall.construct however I'm struggling to come up with a MethodDescription for the constructor of the to be generated class.
If you cannot describe the generated class, you can create an instance of InstrumentedType.Default and provide it as a type description.
You should use MethodCall which allows you to access parameters from its DSL.

Why does ByteBuddy tell me that there is an ambiguity in my interceptor methods when there is only one such method?

(Trying to keep this simple.)
I have a (partial) ByteBuddy recipe like this:
builder
.method(someMatcher())
.intercept(MethodDelegation.to(this.interceptor));
I have an "interceptor" class defined like this:
private static final class Interceptor {
private Interceptor() {
super();
}
#RuntimeType
private final Object doSomething(#This final Proxy<?> proxy,
#SuperCall final Callable<?> callable,
#Origin final String methodSignature) throws Exception {
final Object proxiedInstance = proxy.getProxiedInstance();
// TODO: logic
return callable.call(); // for now
}
}
(The interceptor method needs to be non-static for various reasons not important here.)
When I create an instance of this ByteBuddy-defined class and call a simple public void blork() method on it, I get:
Cannot resolve ambiguous delegation of public void com.foo.TestExplorations$Frob.blork() to net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Builder$Build#3d101b05 or net.bytebuddy.implementation.bind.MethodDelegationBinder$MethodBinding$Builder$Build#1a9efd25
How can there be ambiguity when there is only one interceptor? What have I done wrong?
Byte Buddy just adds a method call to the instrumented class which needs to be able to see the target class. If it is private, it is ignored and Byte Buddy searches further up the hierarchy where it finally consideres the methods of Object which are all equally unsuited but therefore an ambiguity exception is thrown instead of an exception that no method could be bound.

NSubstitute throws CouldNotSetReturnDueToTypeMismatchException when mocking Query on NHibernate Session

I have a repository offering a GetAll method which again calls the Query extension method on the ISession instance of NHibernate.
public ICollection<Product> GetAll()
{
return _session.Query<Product>().ToList();
}
My unit test looks like this:
[Test]
public void GetAllReturnsCollectionFromSession()
{
IQueryable<Product> productList = new ProductListBuilder().Build().AsQueryable();
_fixture.Session.Query<Product>().Returns(productList);
var sut = _fixture.CreateSut();
var result = sut.GetAll();
Assert.AreSame(productList, result);
_fixture.Session.Received().Query<Product>();
}
In the _fixture.Session.Query().Returns(productList) statement, NSubstitute throws the following exception:
NSubstitute.Exceptions.CouldNotSetReturnDueToTypeMismatchException : Can not return value of type IQueryable`1Proxy for ISession.GetSessionImplementation (expected type ISessionImplementor).
Make sure you called Returns() after calling your substitute (for example: mySub.SomeMethod().Returns(value)),
and that you are not configuring other substitutes within Returns() (for example, avoid this: mySub.SomeMethod().Returns(ConfigOtherSub())).
If you substituted for a class rather than an interface, check that the call to your substitute was on a virtual/abstract member.
Return values cannot be configured for non-virtual/non-abstract members.
Correct use:
mySub.SomeMethod().Returns(returnValue);
Potentially problematic use:
mySub.SomeMethod().Returns(ConfigOtherSub());
Instead try:
var returnValue = ConfigOtherSub();
mySub.SomeMethod().Returns(returnValue);
at NSubstitute.Core.ConfigureCall.CheckResultIsCompatibleWithCall(IReturn valueToReturn, ICallSpecification spec)
at NSubstitute.Core.ConfigureCall.SetResultForLastCall(IReturn valueToReturn, MatchArgs matchArgs)
at NSubstitute.Core.CallRouter.LastCallShouldReturn(IReturn returnValue, MatchArgs matchArgs)
at NSubstitute.Core.SubstitutionContext.LastCallShouldReturn(IReturn value, MatchArgs matchArgs)
at NSubstitute.SubstituteExtensions.Returns[T](MatchArgs matchArgs, T returnThis, T[] returnThese)
at NSubstitute.SubstituteExtensions.ReturnsForAnyArgs[T](T value, T returnThis, T[] returnThese)
at Statoil.Wellcom.DataLayer.Implementation.Oracle.UnitTests.Repositories.DwapplicationRepositoryTests.GetAllReturnsCollectionFromSession() in C:\git\WELLCOM\source\Statoil.Wellcom.DataLayer.Implementation.Oracle.UnitTests\Repositories\DwapplicationRepositoryTests.cs:line 123
It looks like NSubstitute is unable to set the return value due to Query being an extension method. How would I go about mocking the extension method call on the ISession?
The easiest solution is to wrap your ISession in another interface/concrete class so you can stub that out:
public interface ISessionWrapper
{
IQueryable<T> Query<T>();
}
public class SessionWrapper : ISessionWrapper
{
private readonly ISession _session;
public SessionWrapper(ISession session)
{
_session = session;
}
public IQueryable<T> Query<T>()
{
return _session.Query<T>();
}
}
There is no way to mock extension method with NSubstitute, however if you know what extension method is using inside, than you can mock that. Your test will use extension method on mocked object and eventually it will use mocked method. Difficult part is to know what is going on inside.
It worked for me in projects, where I knew all the source code and I could check what's inside.

how to pass context(ApplicationContext) as an argument to logBefore Method(Method of Aspect)?

I want to implement an aspect to avoid lazy Loading problems .
so how to pass context(Application Context) as an argument for logBefore Method?
What is the signature pointcut defines the method above(the place to which I make "???"), knowing that this method (logBefore) will be execute before all the methods that have "Set"
as a result type and "slm.aoa.buisiness.facade" as package
#Aspect
public class EagerLoading {
#Before("execution(???)")
public void logBefore(JoinPoint joinPoint) {
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
and Thanks for your Help .