All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.testifyproject.bytebuddy.asm.Advice Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
package org.testifyproject.bytebuddy.asm;

import lombok.EqualsAndHashCode;
import org.testifyproject.bytebuddy.ClassFileVersion;
import org.testifyproject.bytebuddy.description.annotation.AnnotationDescription;
import org.testifyproject.bytebuddy.description.enumeration.EnumerationDescription;
import org.testifyproject.bytebuddy.description.field.FieldDescription;
import org.testifyproject.bytebuddy.description.method.MethodDescription;
import org.testifyproject.bytebuddy.description.method.MethodList;
import org.testifyproject.bytebuddy.description.method.ParameterDescription;
import org.testifyproject.bytebuddy.description.method.ParameterList;
import org.testifyproject.bytebuddy.description.type.TypeDefinition;
import org.testifyproject.bytebuddy.description.type.TypeDescription;
import org.testifyproject.bytebuddy.description.type.TypeList;
import org.testifyproject.bytebuddy.dynamic.ClassFileLocator;
import org.testifyproject.bytebuddy.dynamic.TargetType;
import org.testifyproject.bytebuddy.dynamic.scaffold.FieldLocator;
import org.testifyproject.bytebuddy.dynamic.scaffold.InstrumentedType;
import org.testifyproject.bytebuddy.implementation.Implementation;
import org.testifyproject.bytebuddy.implementation.SuperMethodCall;
import org.testifyproject.bytebuddy.implementation.bytecode.*;
import org.testifyproject.bytebuddy.implementation.bytecode.assign.Assigner;
import org.testifyproject.bytebuddy.implementation.bytecode.collection.ArrayAccess;
import org.testifyproject.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import org.testifyproject.bytebuddy.implementation.bytecode.constant.*;
import org.testifyproject.bytebuddy.implementation.bytecode.member.FieldAccess;
import org.testifyproject.bytebuddy.implementation.bytecode.member.MethodInvocation;
import org.testifyproject.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import org.testifyproject.bytebuddy.matcher.ElementMatcher;
import org.testifyproject.bytebuddy.pool.TypePool;
import org.testifyproject.bytebuddy.utility.CompoundList;
import org.testifyproject.bytebuddy.utility.JavaType;
import org.testifyproject.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor;
import org.testifyproject.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor;
import org.testifyproject.bytebuddy.utility.visitor.StackAwareMethodVisitor;
import org.testifyproject.bytebuddy.jar.asm.*;

import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

import static org.testifyproject.bytebuddy.matcher.ElementMatchers.named;

/**
 * 

* Advice wrappers copy the code of blueprint methods to be executed before and/or after a matched method. To achieve this, a {@code static} * method of a class is annotated by {@link OnMethodEnter} and/or {@link OnMethodExit} and provided to an instance of this class. *

*

* A method that is annotated with {@link OnMethodEnter} can annotate its parameters with {@link Argument} where field access to this parameter * is substituted with access to the specified argument of the instrumented method. Alternatively, a parameter can be annotated by {@link This} * where the {@code this} reference of the instrumented method is read when the parameter is accessed. This mechanism can also be used to assign a * new value to the {@code this} reference of an instrumented method. If no annotation is used on a parameter, it is assigned the {@code n}-th * parameter of the instrumented method for the {@code n}-th parameter of the advice method. All parameters must declare the exact same type as * the parameters of the instrumented type or the method's declaring type for the {@link This} reference respectively if they are not marked as * read-only. In the latter case, it suffices that a parameter type is a super type of the corresponding type of the instrumented method. *

*

* A method that is annotated with {@link OnMethodExit} can equally annotate its parameters with {@link Argument} and {@link This}. Additionally, * it can annotate a parameter with {@link Return} to receive the original method's return value. By reassigning the return value, it is possible * to replace the returned value. If an instrumented method does not return a value, this annotation must not be used. If a method returns * exceptionally, the parameter is set to its default value, i.e. to {@code 0} for primitive types and to {@code null} for reference types. The * parameter's type must be equal to the instrumented method's return type if it is not set to read-only where it suffices to declare the * parameter type to be of any super type to the instrumented method's return type. An exception can be read by annotating a parameter of type * {@link Throwable} annotated with {@link Thrown} which is assigned the thrown {@link Throwable} or {@code null} if a method returns normally. * Doing so, it is possible to exchange a thrown exception with any checked or unchecked exception.Finally, if a method annotated with * {@link OnMethodEnter} exists and this method returns a value, this value can be accessed by a parameter annotated with {@link Enter}. * This parameter must declare the same type as type being returned by the method annotated with {@link OnMethodEnter}. If the parameter is marked * to be read-only, it suffices that the annotated parameter is of a super type of the return type of the method annotated by * {@link OnMethodEnter}. If no such method exists or this method returns {@code void}, no such parameter must be declared. Any return value * of a method that is annotated by {@link OnMethodExit} is discarded. *

*

* If any advice method throws an exception, the method is terminated prematurely. If the method annotated by {@link OnMethodEnter} throws an exception, * the method annotated by {@link OnMethodExit} method is not invoked. If the instrumented method throws an exception, the method that is annotated by * {@link OnMethodExit} is only invoked if the {@link OnMethodExit#onThrowable()} property is set to {@code true} what is the default. If this property * is set to {@code false}, the {@link Thrown} annotation must not be used on any parameter. *

*

* Byte Buddy does not assert the visibility of any types that are referenced within an inlined advice method. It is the responsibility of * the user of this class to assure that all types referenced within the advice methods are visible to the instrumented class. Failing to * do so results in a {@link IllegalAccessError} at the instrumented class's runtime. *

*

* Advice can be used either as a {@link AsmVisitorWrapper} where any declared methods of the currently instrumented type are enhanced without * replacing an existing implementation. Alternatively, advice can function as an {@link Implementation} where, by default, the original super * or default method of the instrumented method is invoked. If this is not possible or undesired, the delegate implementation can be changed * by specifying a wrapped implementation explicitly by {@link Advice#wrap(Implementation)}. *

*

* When using an advice class as a visitor wrapper, native or abstract methods which are silently skipped when advice matches such a method. *

*

* Important: Since Java 6, class files contain stack map frames embedded into a method's byte code. When advice methods are compiled * with a class file version less then Java 6 but are used for a class file that was compiled to Java 6 or newer, these stack map frames must be * computed by ASM by using the {@link ClassWriter#COMPUTE_FRAMES} option. If the advice methods do not contain any branching instructions, this is * not required. No action is required if the advice methods are at least compiled with Java 6 but are used on classes older than Java 6. This * limitation only applies to advice methods that are inlined. Also, it is the responsibility of this class's user to assure that the advice method * does not contain byte code constructs that are not supported by the class containing the instrumented method. In particular, pre Java-5 * try-finally blocks cannot be inlined into classes with newer byte code levels as the jsr instruction was deprecated. Also, classes prior * to Java 7 do not support the invokedynamic command which must not be contained by an advice method if the instrumented method targets an * older class file format version. *

*

* Note: For the purpose of inlining, Java 5 and Java 6 byte code can be seen as the best candidate for advice methods. These versions do * no longer allow subroutines, neither do they already allow invokedynamic instructions or method handles. This way, Java 5 and Java 6 byte * code is compatible to both older and newer versions. One exception for backwards-incompatible byte code is the possibility to load type references * from the constant pool onto the operand stack. These instructions can however easily be transformerd for classes compiled to Java 4 and older * by registering a {@link TypeConstantAdjustment} before the advice visitor. *

*

* Note: It is not possible to trigger break points in inlined advice methods as the debugging information of the inlined advice is not * preserved. It is not possible in Java to reference more than one source file per class what makes translating such debugging information * impossible. It is however possible to set break points in advice methods when invoking the original advice target. This allows debugging * of advice code within unit tests that invoke the advice method without instrumentation. As a consequence of not transferring debugging information, * the names of the parameters of an advice method do not matter when inlining, neither does any meta information on the advice method's body * such as annotations or parameter modifiers. *

*

* Note: The behavior of this component is undefined if it is supplied with invalid byte code what might result in runtime exceptions. *

*

* Note: When using advice from a Java agent with an {@link org.testifyproject.bytebuddy.agent.builder.AgentBuilder}, it often makes sense to not include * any library-specific code in the agent's jar file. For being able to locate the advice code in the context of the library dependencies, Byte * Buddy offers an {@link org.testifyproject.bytebuddy.agent.builder.AgentBuilder.Transformer.ForAdvice} implementation that allows registering the agent's * class file locators for assembly of the advice class's description at runtime and with respect to the specific user dependencies. *

* * @see OnMethodEnter * @see OnMethodExit */ @EqualsAndHashCode public class Advice implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper, Implementation { /** * Indicates that no class reader is available to an adice method. */ private static final ClassReader UNDEFINED = null; /** * A reference to the {@link OnMethodEnter#inline()} method. */ private static final MethodDescription.InDefinedShape INLINE_ENTER; /** * A reference to the {@link OnMethodEnter#suppress()} method. */ private static final MethodDescription.InDefinedShape SUPPRESS_ENTER; /** * A reference to the {@link OnMethodEnter#prependLineNumber()} method. */ private static final MethodDescription.InDefinedShape PREPEND_LINE_NUMBER; /** * A reference to the {@link OnMethodEnter#skipOn()} method. */ private static final MethodDescription.InDefinedShape SKIP_ON; /** * A reference to the {@link OnMethodExit#inline()} method. */ private static final MethodDescription.InDefinedShape INLINE_EXIT; /** * A reference to the {@link OnMethodExit#suppress()} method. */ private static final MethodDescription.InDefinedShape SUPPRESS_EXIT; /** * A reference to the {@link OnMethodExit#onThrowable()} method. */ private static final MethodDescription.InDefinedShape ON_THROWABLE; /* * Extracts the annotation values for the enter and exit advice annotations. */ static { MethodList enter = new TypeDescription.ForLoadedType(OnMethodEnter.class).getDeclaredMethods(); INLINE_ENTER = enter.filter(named("inline")).getOnly(); SUPPRESS_ENTER = enter.filter(named("suppress")).getOnly(); SKIP_ON = enter.filter(named("skipOn")).getOnly(); PREPEND_LINE_NUMBER = enter.filter(named("prependLineNumber")).getOnly(); MethodList exit = new TypeDescription.ForLoadedType(OnMethodExit.class).getDeclaredMethods(); INLINE_EXIT = exit.filter(named("inline")).getOnly(); SUPPRESS_EXIT = exit.filter(named("suppress")).getOnly(); ON_THROWABLE = exit.filter(named("onThrowable")).getOnly(); } /** * The dispatcher for instrumenting the instrumented method upon entering. */ private final Dispatcher.Resolved.ForMethodEnter methodEnter; /** * The dispatcher for instrumenting the instrumented method upon exiting. */ private final Dispatcher.Resolved.ForMethodExit methodExit; /** * The assigner to use. */ private final Assigner assigner; /** * The stack manipulation to apply within a suppression handler. */ private final StackManipulation exceptionHandler; /** * The delegate implementation to apply if this advice is used as an instrumentation. */ private final Implementation delegate; /** * Creates a new advice. * * @param methodEnter The dispatcher for instrumenting the instrumented method upon entering. * @param methodExit The dispatcher for instrumenting the instrumented method upon exiting. */ protected Advice(Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit) { this(methodEnter, methodExit, Assigner.DEFAULT, Removal.of(TypeDescription.THROWABLE), SuperMethodCall.INSTANCE); } /** * Creates a new advice. * * @param methodEnter The dispatcher for instrumenting the instrumented method upon entering. * @param methodExit The dispatcher for instrumenting the instrumented method upon exiting. * @param assigner The assigner to use. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @param delegate The delegate implementation to apply if this advice is used as an instrumentation. */ private Advice(Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, Assigner assigner, StackManipulation exceptionHandler, Implementation delegate) { this.methodEnter = methodEnter; this.methodExit = methodExit; this.assigner = assigner; this.exceptionHandler = exceptionHandler; this.delegate = delegate; } /** * Implements advice where every matched method is advised by the given type's advisory methods. The advices binary representation is * accessed by querying the class loader of the supplied class for a class file. * * @param advice The type declaring the advice. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(Class advice) { return to(advice, ClassFileLocator.ForClassLoader.of(advice.getClassLoader())); } /** * Implements advice where every matched method is advised by the given type's advisory methods. * * @param advice The type declaring the advice. * @param classFileLocator The class file locator for locating the advisory class's class file. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(Class advice, ClassFileLocator classFileLocator) { return to(new TypeDescription.ForLoadedType(advice), classFileLocator); } /** * Implements advice where every matched method is advised by the given type's advisory methods. Using this method, a non-operational * class file locator is specified for the advice target. This implies that only advice targets with the inline target set * to {@code false} are resolvable by the returned instance. * * @param advice The type declaring the advice. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(TypeDescription advice) { return to(advice, ClassFileLocator.NoOp.INSTANCE); } /** * Implements advice where every matched method is advised by the given type's advisory methods. * * @param advice A description of the type declaring the advice. * @param classFileLocator The class file locator for locating the advisory class's class file. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(TypeDescription advice, ClassFileLocator classFileLocator) { return to(advice, classFileLocator, Collections.>emptyList()); } /** * Creates a new advice. * * @param advice A description of the type declaring the advice. * @param classFileLocator The class file locator for locating the advisory class's class file. * @param userFactories A list of custom factories for user generated offset mappings. * @return A method visitor wrapper representing the supplied advice. */ protected static Advice to(TypeDescription advice, ClassFileLocator classFileLocator, List> userFactories) { Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE; for (MethodDescription.InDefinedShape methodDescription : advice.getDeclaredMethods()) { methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription); methodExit = locate(OnMethodExit.class, INLINE_EXIT, methodExit, methodDescription); } if (!methodEnter.isAlive() && !methodExit.isAlive()) { throw new IllegalArgumentException("No advice defined by " + advice); } try { ClassReader classReader = methodEnter.isBinary() || methodExit.isBinary() ? new ClassReader(classFileLocator.locate(advice.getName()).resolve()) : UNDEFINED; Dispatcher.Resolved.ForMethodEnter resolved = methodEnter.asMethodEnter(userFactories, classReader); return new Advice(resolved, methodExit.asMethodExitTo(userFactories, classReader, resolved)); } catch (IOException exception) { throw new IllegalStateException("Error reading class file of " + advice, exception); } } /** * Implements advice where every matched method is advised by the given type's advisory methods. The advices binary representation is * accessed by querying the class loader of the supplied class for a class file. * * @param enterAdvice The type declaring the enter advice. * @param exitAdvice The type declaring the exit advice. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(Class enterAdvice, Class exitAdvice) { ClassLoader enterLoader = enterAdvice.getClassLoader(), exitLoader = exitAdvice.getClassLoader(); return to(enterAdvice, exitAdvice, enterLoader == exitLoader ? ClassFileLocator.ForClassLoader.of(enterLoader) : new ClassFileLocator.Compound(ClassFileLocator.ForClassLoader.of(enterLoader), ClassFileLocator.ForClassLoader.of(exitLoader))); } /** * Implements advice where every matched method is advised by the given type's advisory methods. * * @param enterAdvice The type declaring the enter advice. * @param exitAdvice The type declaring the exit advice. * @param classFileLocator The class file locator for locating the advisory class's class file. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(Class enterAdvice, Class exitAdvice, ClassFileLocator classFileLocator) { return to(new TypeDescription.ForLoadedType(enterAdvice), new TypeDescription.ForLoadedType(exitAdvice), classFileLocator); } /** * Implements advice where every matched method is advised by the given type's advisory methods. Using this method, a non-operational * class file locator is specified for the advice target. This implies that only advice targets with the inline target set * to {@code false} are resolvable by the returned instance. * * @param enterAdvice The type declaring the enter advice. * @param exitAdvice The type declaring the exit advice. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice) { return to(enterAdvice, exitAdvice, ClassFileLocator.NoOp.INSTANCE); } /** * Implements advice where every matched method is advised by the given type's advisory methods. * * @param enterAdvice The type declaring the enter advice. * @param exitAdvice The type declaring the exit advice. * @param classFileLocator The class file locator for locating the advisory class's class file. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice, ClassFileLocator classFileLocator) { return to(enterAdvice, exitAdvice, classFileLocator, Collections.>emptyList()); } /** * Creates a new advice. * * @param enterAdvice The type declaring the enter advice. * @param exitAdvice The type declaring the exit advice. * @param classFileLocator The class file locator for locating the advisory class's class file. * @param userFactories A list of custom factories for user generated offset mappings. * @return A method visitor wrapper representing the supplied advice. */ protected static Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice, ClassFileLocator classFileLocator, List> userFactories) { Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE; for (MethodDescription.InDefinedShape methodDescription : enterAdvice.getDeclaredMethods()) { methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription); } if (!methodEnter.isAlive()) { throw new IllegalArgumentException("No enter advice defined by " + enterAdvice); } for (MethodDescription.InDefinedShape methodDescription : exitAdvice.getDeclaredMethods()) { methodExit = locate(OnMethodExit.class, INLINE_EXIT, methodExit, methodDescription); } if (!methodExit.isAlive()) { throw new IllegalArgumentException("No enter advice defined by " + exitAdvice); } try { Dispatcher.Resolved.ForMethodEnter resolved = methodEnter.asMethodEnter(userFactories, methodEnter.isBinary() ? new ClassReader(classFileLocator.locate(enterAdvice.getName()).resolve()) : UNDEFINED); return new Advice(resolved, methodExit.asMethodExitTo(userFactories, methodExit.isBinary() ? new ClassReader(classFileLocator.locate(exitAdvice.getName()).resolve()) : UNDEFINED, resolved)); } catch (IOException exception) { throw new IllegalStateException("Error reading class file of " + enterAdvice + " or " + exitAdvice, exception); } } /** * Locates a dispatcher for the method if available. * * @param type The annotation type that indicates a given form of advice that is currently resolved. * @param property An annotation property that indicates if the advice method should be inlined. * @param dispatcher Any previous dispatcher that was discovered or {@code null} if no such dispatcher was yet found. * @param methodDescription The method description that is considered as an advice method. * @return A resolved dispatcher or {@code null} if no dispatcher was resolved. */ private static Dispatcher.Unresolved locate(Class type, MethodDescription.InDefinedShape property, Dispatcher.Unresolved dispatcher, MethodDescription.InDefinedShape methodDescription) { AnnotationDescription annotation = methodDescription.getDeclaredAnnotations().ofType(type); if (annotation == null) { return dispatcher; } else if (dispatcher.isAlive()) { throw new IllegalStateException("Duplicate advice for " + dispatcher + " and " + methodDescription); } else if (!methodDescription.isStatic()) { throw new IllegalStateException("Advice for " + methodDescription + " is not static"); } else { return annotation.getValue(property).resolve(Boolean.class) ? new Dispatcher.Inlining(methodDescription) : new Dispatcher.Delegating(methodDescription); } } /** * Allows for the configuration of custom annotations that are then bound to a dynamically computed, constant value. * * @return A builder for an {@link Advice} instrumentation with custom values. * @see OffsetMapping.Factory */ public static WithCustomMapping withCustomMapping() { return new WithCustomMapping(); } /** * Returns an ASM visitor wrapper that matches the given matcher and applies this advice to the matched methods. * * @param matcher The matcher identifying methods to apply the advice to. * @return A suitable ASM visitor wrapper with the compute frames option enabled. */ public AsmVisitorWrapper.ForDeclaredMethods on(ElementMatcher matcher) { return new AsmVisitorWrapper.ForDeclaredMethods().method(matcher, this); } @Override public MethodVisitor wrap(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, TypePool typePool, int writerFlags, int readerFlags) { return instrumentedMethod.isAbstract() || instrumentedMethod.isNative() ? methodVisitor : doWrap(instrumentedType, instrumentedMethod, methodVisitor, implementationContext, writerFlags, readerFlags); } /** * Wraps the method visitor to implement this advice. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param methodVisitor The method visitor to write to. * @param implementationContext The implementation context to use. * @param writerFlags The ASM writer flags to use. * @param readerFlags The ASM reader flags to use. * @return A method visitor that applies this advice. */ protected MethodVisitor doWrap(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, int writerFlags, int readerFlags) { methodVisitor = methodEnter.isPrependLineNumber() ? new LineNumberPrependingMethodVisitor(methodVisitor) : methodVisitor; if (!methodExit.isAlive()) { return new AdviceVisitor.WithoutExitAdvice(methodVisitor, implementationContext, assigner, exceptionHandler, instrumentedType, instrumentedMethod, methodEnter, writerFlags, readerFlags); } else if (methodExit.getThrowable().represents(NoExceptionHandler.class)) { return new AdviceVisitor.WithExitAdvice.WithoutExceptionHandling(methodVisitor, implementationContext, assigner, exceptionHandler, instrumentedType, instrumentedMethod, methodEnter, methodExit, writerFlags, readerFlags); } else if (instrumentedMethod.isConstructor()) { throw new IllegalStateException("Cannot catch exception during constructor call for " + instrumentedMethod); } else { return new AdviceVisitor.WithExitAdvice.WithExceptionHandling(methodVisitor, implementationContext, assigner, exceptionHandler, instrumentedType, instrumentedMethod, methodEnter, methodExit, writerFlags, readerFlags, methodExit.getThrowable()); } } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return delegate.prepare(instrumentedType); } @Override public ByteCodeAppender appender(Target implementationTarget) { return new Appender(this, implementationTarget, delegate.appender(implementationTarget)); } /** * Configures this advice to use the specified assigner. Any previous or default assigner is replaced. * * @param assigner The assigner to use, * @return A version of this advice that uses the specified assigner. */ public Advice withAssigner(Assigner assigner) { return new Advice(methodEnter, methodExit, assigner, exceptionHandler, delegate); } /** * Configures this advice to call {@link Throwable#printStackTrace()} upon a suppressed exception. * * @return A version of this advice that prints any suppressed exception. */ public Advice withExceptionPrinting() { try { return withExceptionHandler(MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Throwable.class.getMethod("printStackTrace")))); } catch (NoSuchMethodException exception) { throw new IllegalStateException("Cannot locate Throwable::printStackTrace"); } } /** * Configures this advice to execute the given stack manipulation upon a suppressed exception. The stack manipulation is executed with a * {@link Throwable} instance on the operand stack. The stack must be empty upon completing the exception handler. * * @param exceptionHandler The exception handler to apply. * @return A version of this advice that applies the supplied exception handler. */ public Advice withExceptionHandler(StackManipulation exceptionHandler) { return new Advice(methodEnter, methodExit, assigner, exceptionHandler, delegate); } /** * Wraps the supplied implementation to have this advice applied around it. * * @param implementation The implementation to wrap. * @return An implementation that applies the supplied implementation and wraps it with this advice. */ public Implementation wrap(Implementation implementation) { return new Advice(methodEnter, methodExit, assigner, exceptionHandler, implementation); } /** * Represents an offset mapping for an advice method to an alternative offset. */ public interface OffsetMapping { /** * Resolves an offset mapping to a given target offset. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method for which the mapping is to be resolved. * @param assigner The assigner to use. * @param context The context in which the offset mapping is applied. * @return A suitable target mapping. */ Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context); /** * A context for applying an {@link OffsetMapping}. */ interface Context { /** * Returns {@code true} if the advice is applied on a fully initialized instance, i.e. describes if the {@code this} * instance is available or still uninitialized during calling the advice. * * @return {@code true} if the advice is applied onto a fully initialized method. */ boolean isInitialized(); /** * Returns the padding before writing additional values that this context applies. * * @return The required padding for this context. */ int getPadding(); /** * A context for an offset mapping describing a method entry. */ enum ForMethodEntry implements Context { /** * Describes a context for a method entry that is not a constructor. */ INITIALIZED(true), /** * Describes a context for a method entry that is a constructor. */ NON_INITIALIZED(false); /** * Resolves an appropriate method entry context for the supplied instrumented method. * * @param instrumentedMethod The instrumented method. * @return An appropriate context. */ protected static Context of(MethodDescription instrumentedMethod) { return instrumentedMethod.isConstructor() ? NON_INITIALIZED : INITIALIZED; } /** * {@code true} if the method is no constructor, i.e. is invoked for an initialized instance upon entry. */ private final boolean initialized; /** * Creates a new context for a method entry. * * @param initialized {@code true} if the method is no constructor, i.e. is invoked for an initialized instance upon entry. */ ForMethodEntry(boolean initialized) { this.initialized = initialized; } @Override public boolean isInitialized() { return initialized; } @Override public int getPadding() { return StackSize.ZERO.getSize(); } } /** * A context for an offset mapping describing a method exit. */ enum ForMethodExit implements Context { /** * A method exit with a zero sized padding. */ ZERO(StackSize.ZERO), /** * A method exit with a single slot padding. */ SINGLE(StackSize.SINGLE), /** * A method exit with a double slot padding. */ DOUBLE(StackSize.DOUBLE); /** * The padding implied by this method exit. */ private final StackSize stackSize; /** * Creates a new context for a method exit. * * @param stackSize The padding implied by this method exit. */ ForMethodExit(StackSize stackSize) { this.stackSize = stackSize; } /** * Resolves an appropriate method exit context for the supplied entry method type. * * @param typeDescription The type that is returned by the enter method. * @return An appropriate context for the supplied entry method type. */ protected static Context of(TypeDefinition typeDescription) { switch (typeDescription.getStackSize()) { case ZERO: return ZERO; case SINGLE: return SINGLE; case DOUBLE: return DOUBLE; default: throw new IllegalStateException("Unknown stack size: " + typeDescription); } } @Override public boolean isInitialized() { return true; } @Override public int getPadding() { return stackSize.getSize(); } } } /** * A target offset of an offset mapping. */ interface Target { /** * Resolves a read instruction. * * @return A stack manipulation that represents a reading of an advice parameter. */ StackManipulation resolveRead(); /** * Resolves a write instruction. * * @return A stack manipulation that represents a writing to an advice parameter. */ StackManipulation resolveWrite(); /** * Resolves an increment instruction. * * @param value The incrementation value. * @return A stack manipulation that represents a writing to an advice parameter. */ StackManipulation resolveIncrement(int value); /** * A target for an offset mapping that represents a non-operational value. All writes are discarded and a value's * default value is returned upon every read. */ @EqualsAndHashCode abstract class ForDefaultValue implements Target { /** * The represented type. */ protected final TypeDefinition typeDefinition; /** * A stack manipulation to apply after a read instruction. */ protected final StackManipulation readAssignment; /** * Creates a new target for a default value. * * @param typeDefinition The represented type. * @param readAssignment A stack manipulation to apply after a read instruction. */ protected ForDefaultValue(TypeDefinition typeDefinition, StackManipulation readAssignment) { this.typeDefinition = typeDefinition; this.readAssignment = readAssignment; } @Override public StackManipulation resolveRead() { return new StackManipulation.Compound(DefaultValue.of(typeDefinition), readAssignment); } /** * A read-only target for a default value. */ protected static class ReadOnly extends ForDefaultValue { /** * Creates a new writable target for a default value. * * @param typeDefinition The represented type. */ protected ReadOnly(TypeDefinition typeDefinition) { this(typeDefinition, StackManipulation.Trivial.INSTANCE); } /** * Creates a new -writable target for a default value. * * @param typeDefinition The represented type. * @param readAssignment A stack manipulation to apply after a read instruction. */ protected ReadOnly(TypeDefinition typeDefinition, StackManipulation readAssignment) { super(typeDefinition, readAssignment); } @Override public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only default value"); } @Override public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to read-only default value"); } } /** * A read-write target for a default value. */ protected static class ReadWrite extends ForDefaultValue { /** * Creates a new read-only target for a default value. * * @param typeDefinition The represented type. */ protected ReadWrite(TypeDefinition typeDefinition) { this(typeDefinition, StackManipulation.Trivial.INSTANCE); } /** * Creates a new read-only target for a default value. * * @param typeDefinition The represented type. * @param readAssignment A stack manipulation to apply after a read instruction. */ protected ReadWrite(TypeDefinition typeDefinition, StackManipulation readAssignment) { super(typeDefinition, readAssignment); } @Override public StackManipulation resolveWrite() { return Removal.of(typeDefinition); } @Override public StackManipulation resolveIncrement(int value) { return StackManipulation.Trivial.INSTANCE; } } } /** * A target for an offset mapping that represents a local variable. */ @EqualsAndHashCode abstract class ForVariable implements Target { /** * The represented type. */ protected final TypeDefinition typeDefinition; /** * The value's offset. */ protected final int offset; /** * An assignment to execute upon reading a value. */ protected final StackManipulation readAssignment; /** * Creates a new target for a local variable mapping. * * @param typeDefinition The represented type. * @param offset The value's offset. * @param readAssignment An assignment to execute upon reading a value. */ protected ForVariable(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment) { this.typeDefinition = typeDefinition; this.offset = offset; this.readAssignment = readAssignment; } @Override public StackManipulation resolveRead() { return new StackManipulation.Compound(MethodVariableAccess.of(typeDefinition).loadFrom(offset), readAssignment); } /** * A target for a read-only mapping of a local variable. */ protected static class ReadOnly extends ForVariable { /** * Creates a read-only mapping for a local variable. * * @param parameterDescription The mapped parameter. * @param readAssignment An assignment to execute upon reading a value. */ protected ReadOnly(ParameterDescription parameterDescription, StackManipulation readAssignment) { this(parameterDescription.getType(), parameterDescription.getOffset(), readAssignment); } /** * Creates a read-only mapping for a local variable. * * @param typeDefinition The represented type. * @param offset The value's offset. * @param readAssignment An assignment to execute upon reading a value. */ protected ReadOnly(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment) { super(typeDefinition, offset, readAssignment); } @Override public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only parameter " + typeDefinition + " at " + offset); } @Override public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to read-only variable " + typeDefinition + " at " + offset); } } /** * A target for a writable mapping of a local variable. */ @EqualsAndHashCode(callSuper = true) protected static class ReadWrite extends ForVariable { /** * A stack manipulation to apply upon a write to the variable. */ private final StackManipulation writeAssignment; /** * Creates a new target mapping for a writable local variable. * * @param parameterDescription The mapped parameter. * @param readAssignment An assignment to execute upon reading a value. * @param writeAssignment A stack manipulation to apply upon a write to the variable. */ protected ReadWrite(ParameterDescription parameterDescription, StackManipulation readAssignment, StackManipulation writeAssignment) { this(parameterDescription.getType(), parameterDescription.getOffset(), readAssignment, writeAssignment); } /** * Creates a new target mapping for a writable local variable. * * @param typeDefinition The represented type. * @param offset The value's offset. * @param readAssignment An assignment to execute upon reading a value. * @param writeAssignment A stack manipulation to apply upon a write to the variable. */ protected ReadWrite(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment, StackManipulation writeAssignment) { super(typeDefinition, offset, readAssignment); this.writeAssignment = writeAssignment; } @Override public StackManipulation resolveWrite() { return new StackManipulation.Compound(writeAssignment, MethodVariableAccess.of(typeDefinition).storeAt(offset)); } @Override public StackManipulation resolveIncrement(int value) { return typeDefinition.represents(int.class) ? MethodVariableAccess.of(typeDefinition).increment(offset, value) : new StackManipulation.Compound(resolveRead(), IntegerConstant.forValue(1), Addition.INTEGER, resolveWrite()); } } } /** * A target mapping for an array of all local variables. */ @EqualsAndHashCode abstract class ForArray implements Target { /** * The compound target type. */ protected final TypeDescription.Generic target; /** * The stack manipulations to apply upon reading a variable array. */ protected final List valueReads; /** * Creates a new target mapping for an array of all local variables. * * @param target The compound target type. * @param valueReads The stack manipulations to apply upon reading a variable array. */ protected ForArray(TypeDescription.Generic target, List valueReads) { this.target = target; this.valueReads = valueReads; } @Override public StackManipulation resolveRead() { return ArrayFactory.forType(target).withValues(valueReads); } @Override public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot increment read-only array value"); } /** * A target mapping for a read-only target mapping for an array of local variables. */ protected static class ReadOnly extends ForArray { /** * Creates a read-only target mapping for an array of all local variables. * * @param target The compound target type. * @param valueReads The stack manipulations to apply upon reading a variable array. */ protected ReadOnly(TypeDescription.Generic target, List valueReads) { super(target, valueReads); } @Override public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only array value"); } } /** * A target mapping for a writable target mapping for an array of local variables. */ @EqualsAndHashCode(callSuper = true) protected static class ReadWrite extends ForArray { /** * The stack manipulations to apply upon writing to a variable array. */ private final List valueWrites; /** * Creates a writable target mapping for an array of all local variables. * * @param target The compound target type. * @param valueReads The stack manipulations to apply upon reading a variable array. * @param valueWrites The stack manipulations to apply upon writing to a variable array. */ protected ReadWrite(TypeDescription.Generic target, List valueReads, List valueWrites) { super(target, valueReads); this.valueWrites = valueWrites; } @Override public StackManipulation resolveWrite() { return ArrayAccess.of(target).forEach(valueWrites); } } } /** * A target for an offset mapping that loads a field value. */ @EqualsAndHashCode abstract class ForField implements Target { /** * The field value to load. */ protected final FieldDescription fieldDescription; /** * The stack manipulation to apply upon a read. */ protected final StackManipulation readAssignment; /** * Creates a new target for a field value mapping. * * @param fieldDescription The field value to load. * @param readAssignment The stack manipulation to apply upon a read. */ protected ForField(FieldDescription fieldDescription, StackManipulation readAssignment) { this.fieldDescription = fieldDescription; this.readAssignment = readAssignment; } @Override public StackManipulation resolveRead() { return new StackManipulation.Compound(fieldDescription.isStatic() ? StackManipulation.Trivial.INSTANCE : MethodVariableAccess.loadThis(), FieldAccess.forField(fieldDescription).read(), readAssignment); } /** * A read-only mapping for a field value. */ static class ReadOnly extends ForField { /** * Creates a new read-only mapping for a field. * * @param fieldDescription The field value to load. * @param readAssignment The stack manipulation to apply upon a read. */ protected ReadOnly(FieldDescription fieldDescription, StackManipulation readAssignment) { super(fieldDescription, readAssignment); } @Override public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only field value"); } @Override public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to read-only field value"); } } /** * A mapping for a writable field. */ @EqualsAndHashCode(callSuper = true) static class ReadWrite extends ForField { /** * An assignment to apply prior to a field write. */ private final StackManipulation writeAssignment; /** * Creates a new target for a writable field. * * @param fieldDescription The field value to load. * @param readAssignment The stack manipulation to apply upon a read. * @param writeAssignment An assignment to apply prior to a field write. */ protected ReadWrite(FieldDescription fieldDescription, StackManipulation readAssignment, StackManipulation writeAssignment) { super(fieldDescription, readAssignment); this.writeAssignment = writeAssignment; } @Override public StackManipulation resolveWrite() { StackManipulation preparation; if (fieldDescription.isStatic()) { preparation = StackManipulation.Trivial.INSTANCE; } else { preparation = new StackManipulation.Compound( MethodVariableAccess.loadThis(), Duplication.SINGLE.flipOver(fieldDescription.getType()), Removal.SINGLE ); } return new StackManipulation.Compound(preparation, FieldAccess.forField(fieldDescription).write()); } @Override public StackManipulation resolveIncrement(int value) { return new StackManipulation.Compound( resolveRead(), IntegerConstant.forValue(value), Addition.INTEGER, resolveWrite() ); } } } /** * A target for an offset mapping that represents a read-only stack manipulation. */ @EqualsAndHashCode class ForStackManipulation implements Target { /** * The represented stack manipulation. */ private final StackManipulation stackManipulation; /** * Creates a new target for an offset mapping for a stack manipulation. * * @param stackManipulation The represented stack manipulation. */ public ForStackManipulation(StackManipulation stackManipulation) { this.stackManipulation = stackManipulation; } /** * Creates a target for a {@link Method} or {@link Constructor} constant. * * @param methodDescription The method or constructor to represent. * @return A mapping for a method or constructor constant. */ public static Target of(MethodDescription.InDefinedShape methodDescription) { return new ForStackManipulation(MethodConstant.forMethod(methodDescription)); } /** * Creates a target for an offset mapping for a type constant. * * @param typeDescription The type constant to represent. * @return A mapping for a type constant. */ public static Target of(TypeDescription typeDescription) { return new ForStackManipulation(ClassConstant.of(typeDescription)); } /** * Creates a target for an offset mapping for a constant value or {@code null}. * * @param value The constant value to represent or {@code null}. * @return An appropriate target for an offset mapping. */ public static Target of(Object value) { if (value == null) { return new ForStackManipulation(NullConstant.INSTANCE); } else if (value instanceof Boolean) { return new ForStackManipulation(IntegerConstant.forValue((Boolean) value)); } else if (value instanceof Byte) { return new ForStackManipulation(IntegerConstant.forValue((Byte) value)); } else if (value instanceof Short) { return new ForStackManipulation(IntegerConstant.forValue((Short) value)); } else if (value instanceof Character) { return new ForStackManipulation(IntegerConstant.forValue((Character) value)); } else if (value instanceof Integer) { return new ForStackManipulation(IntegerConstant.forValue((Integer) value)); } else if (value instanceof Long) { return new ForStackManipulation(LongConstant.forValue((Long) value)); } else if (value instanceof Float) { return new ForStackManipulation(FloatConstant.forValue((Float) value)); } else if (value instanceof Double) { return new ForStackManipulation(DoubleConstant.forValue((Double) value)); } else if (value instanceof String) { return new ForStackManipulation(new TextConstant((String) value)); } else { throw new IllegalArgumentException("Not a constant value: " + value); } } @Override public StackManipulation resolveRead() { return stackManipulation; } @Override public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to constant value: " + stackManipulation); } @Override public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to constant value: " + stackManipulation); } } } /** * Represents a factory for creating a {@link OffsetMapping} for a given parameter for a given annotation. * * @param the annotation type that triggers this factory. */ interface Factory { /** * Returns the annotation type of this factory. * * @return The factory's annotation type. */ Class getAnnotationType(); /** * Creates a new offset mapping for the supplied parameter if possible. * * @param target The parameter description for which to resolve an offset mapping. * @param annotation The annotation that triggered this factory. * @param adviceType {@code true} if the binding is applied using advice method delegation. * @return A resolved offset mapping or {@code null} if no mapping can be resolved for this parameter. */ OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType); /** * Describes the type of advice being applied. */ enum AdviceType { /** * Indicates advice where the invocation is delegated. */ DELEGATION(true), /** * Indicates advice where the invocation's code is copied into the target method. */ INLINING(false); /** * {@code true} if delegation is used. */ private final boolean delegation; /** * Creates a new advice type. * * @param delegation {@code true} if delegation is used. */ AdviceType(boolean delegation) { this.delegation = delegation; } /** * Returns {@code true} if delegation is used. * * @return {@code true} if delegation is used. */ public boolean isDelegation() { return delegation; } } /** * A simple factory that binds a constant offset mapping. * * @param */ @EqualsAndHashCode class Simple implements Factory { /** * The annotation type being bound. */ private final Class annotationType; /** * The fixed offset mapping. */ private final OffsetMapping offsetMapping; /** * Creates a simple factory for a simple binding for an offset mapping. * * @param annotationType The annotation type being bound. * @param offsetMapping The fixed offset mapping. */ public Simple(Class annotationType, OffsetMapping offsetMapping) { this.annotationType = annotationType; this.offsetMapping = offsetMapping; } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return offsetMapping; } } /** * A factory for an annotation whose use is not permitted. * * @param The annotation type this factory binds. */ @EqualsAndHashCode class Illegal implements Factory { /** * The annotation type. */ private final Class annotationType; /** * Creates a factory that does not permit the usage of the represented annotation. * * @param annotationType The annotation type. */ public Illegal(Class annotationType) { this.annotationType = annotationType; } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { throw new IllegalStateException("Usage of " + annotationType + " is not allowed on " + target); } } } /** * An offset mapping for a given parameter of the instrumented method. */ @EqualsAndHashCode abstract class ForArgument implements OffsetMapping { /** * The type expected by the advice method. */ protected final TypeDescription.Generic target; /** * Determines if the parameter is to be treated as read-only. */ protected final boolean readOnly; /** * The typing to apply when assigning values. */ private final Assigner.Typing typing; /** * Creates a new offset mapping for a parameter of the instrumented method. * * @param target The type expected by the advice method. * @param readOnly Determines if the parameter is to be treated as read-only. * @param typing The typing to apply. */ protected ForArgument(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) { this.target = target; this.readOnly = readOnly; this.typing = typing; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { ParameterDescription parameterDescription = resolve(instrumentedMethod); StackManipulation readAssignment = assigner.assign(parameterDescription.getType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target); } else if (readOnly) { return new Target.ForVariable.ReadOnly(parameterDescription, readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, parameterDescription.getType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target); } return new Target.ForVariable.ReadWrite(parameterDescription, readAssignment, writeAssignment); } } /** * Resolves the bound parameter. * * @param instrumentedMethod The instrumented method. * @return The bound parameter. */ protected abstract ParameterDescription resolve(MethodDescription instrumentedMethod); /** * An offset mapping for a parameter of the instrumented method with a specific index. */ @EqualsAndHashCode(callSuper = true) public static class Unresolved extends ForArgument { /** * The index of the parameter. */ private final int index; /** * {@code true} if the parameter binding is optional. */ private final boolean optional; /** * Creates a new offset binding for a parameter with a given index. * * @param target The target type. * @param argument The annotation that triggers this binding. */ protected Unresolved(TypeDescription.Generic target, Argument argument) { this(target, argument.readOnly(), argument.typing(), argument.value(), argument.optional()); } /** * Creates a new offset binding for a parameter with a given index. * * @param parameterDescription The parameter triggering this binding. */ protected Unresolved(ParameterDescription parameterDescription) { this(parameterDescription.getType(), true, Assigner.Typing.STATIC, parameterDescription.getIndex()); } /** * Creates a non-optional offset binding for a parameter with a given index. * * @param target The type expected by the advice method. * @param readOnly Determines if the parameter is to be treated as read-only. * @param typing The typing to apply. * @param index The index of the parameter. */ public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, int index) { this(target, readOnly, typing, index, false); } /** * Creates a new offset binding for a parameter with a given index. * * @param target The type expected by the advice method. * @param readOnly Determines if the parameter is to be treated as read-only. * @param typing The typing to apply. * @param index The index of the parameter. * @param optional {@code true} if the parameter binding is optional. */ public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, int index, boolean optional) { super(target, readOnly, typing); this.index = index; this.optional = optional; } @Override protected ParameterDescription resolve(MethodDescription instrumentedMethod) { ParameterList parameters = instrumentedMethod.getParameters(); if (parameters.size() <= index) { throw new IllegalStateException(instrumentedMethod + " does not define an index " + index); } else { return parameters.get(index); } } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { if (optional && instrumentedMethod.getParameters().size() <= index) { return readOnly ? new Target.ForDefaultValue.ReadOnly(target) : new Target.ForDefaultValue.ReadWrite(target); } return super.resolve(instrumentedType, instrumentedMethod, assigner, context); } /** * A factory for a mapping of a parameter of the instrumented method. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public Class getAnnotationType() { return Argument.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) { throw new IllegalStateException("Cannot define writable field access for " + target + " when using delegation"); } else { return new ForArgument.Unresolved(target.getType(), annotation.loadSilent()); } } } } /** * An offset mapping for a specific parameter of the instrumented method. */ @EqualsAndHashCode(callSuper = true) public static class Resolved extends ForArgument { /** * The parameter being bound. */ private final ParameterDescription parameterDescription; /** * Creates an offset mapping that binds a parameter of the instrumented method. * * @param target The type expected by the advice method. * @param readOnly Determines if the parameter is to be treated as read-only. * @param typing The typing to apply. * @param parameterDescription The parameter being bound. */ public Resolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, ParameterDescription parameterDescription) { super(target, readOnly, typing); this.parameterDescription = parameterDescription; } @Override protected ParameterDescription resolve(MethodDescription instrumentedMethod) { if (!parameterDescription.getDeclaringMethod().equals(instrumentedMethod)) { throw new IllegalStateException(parameterDescription + " is not a parameter of " + instrumentedMethod); } return parameterDescription; } /** * A factory for a parameter argument of the instrumented method. * * @param The type of the bound annotation. */ @EqualsAndHashCode public static class Factory implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * The bound parameter. */ private final ParameterDescription parameterDescription; /** * {@code true} if the factory should create a read-only binding. */ private final boolean readOnly; /** * The typing to use. */ private final Assigner.Typing typing; /** * Creates a new factory for binding a parameter of the instrumented method with read-only semantics and static typing. * * @param annotationType The annotation type. * @param parameterDescription The bound parameter. */ public Factory(Class annotationType, ParameterDescription parameterDescription) { this(annotationType, parameterDescription, true, Assigner.Typing.STATIC); } /** * Creates a new factory for binding a parameter of the instrumented method. * * @param annotationType The annotation type. * @param parameterDescription The bound parameter. * @param readOnly {@code true} if the factory should create a read-only binding. * @param typing The typing to use. */ public Factory(Class annotationType, ParameterDescription parameterDescription, boolean readOnly, Assigner.Typing typing) { this.annotationType = annotationType; this.parameterDescription = parameterDescription; this.readOnly = readOnly; this.typing = typing; } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new Resolved(target.getType(), readOnly, typing, parameterDescription); } } } } /** * An offset mapping that provides access to the {@code this} reference of the instrumented method. */ @EqualsAndHashCode class ForThisReference implements OffsetMapping { /** * The offset of the {@code this} reference. */ private static final int THIS_REFERENCE = 0; /** * The type that the advice method expects for the {@code this} reference. */ private final TypeDescription.Generic target; /** * Determines if the parameter is to be treated as read-only. */ private final boolean readOnly; /** * The typing to apply. */ private final Assigner.Typing typing; /** * {@code true} if the parameter should be bound to {@code null} if the instrumented method is static. */ private final boolean optional; /** * Creates a new offset mapping for a {@code this} reference. * * @param target The type that the advice method expects for the {@code this} reference. * @param annotation The mapped annotation. */ protected ForThisReference(TypeDescription.Generic target, This annotation) { this(target, annotation.readOnly(), annotation.typing(), annotation.optional()); } /** * Creates a new offset mapping for a {@code this} reference. * * @param target The type that the advice method expects for the {@code this} reference. * @param readOnly Determines if the parameter is to be treated as read-only. * @param typing The typing to apply. * @param optional {@code true} if the parameter should be bound to {@code null} if the instrumented method is static. */ public ForThisReference(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, boolean optional) { this.target = target; this.readOnly = readOnly; this.typing = typing; this.optional = optional; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { if (instrumentedMethod.isStatic() || !context.isInitialized()) { if (optional) { return readOnly ? new Target.ForDefaultValue.ReadOnly(instrumentedType) : new Target.ForDefaultValue.ReadWrite(instrumentedType); } else { throw new IllegalStateException("Cannot map this reference for static method or constructor start: " + instrumentedMethod); } } StackManipulation readAssignment = assigner.assign(instrumentedType.asGenericType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + instrumentedType + " to " + target); } else if (readOnly) { return new Target.ForVariable.ReadOnly(instrumentedType.asGenericType(), THIS_REFERENCE, readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, instrumentedType.asGenericType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + instrumentedType); } return new Target.ForVariable.ReadWrite(instrumentedType.asGenericType(), THIS_REFERENCE, readAssignment, writeAssignment); } } /** * A factory for creating a {@link ForThisReference} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public Class getAnnotationType() { return This.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) { throw new IllegalStateException("Cannot write to this reference for " + target + " in read-only context"); } else { return new ForThisReference(target.getType(), annotation.loadSilent()); } } } } /** * An offset mapping that maps an array containing all arguments of the instrumented method. */ @EqualsAndHashCode class ForAllArguments implements OffsetMapping { /** * The component target type. */ private final TypeDescription.Generic target; /** * {@code true} if the array is read-only. */ private final boolean readOnly; /** * The typing to apply. */ private final Assigner.Typing typing; /** * Creates a new offset mapping for an array containing all arguments. * * @param target The component target type. * @param annotation The mapped annotation. */ protected ForAllArguments(TypeDescription.Generic target, AllArguments annotation) { this(target, annotation.readOnly(), annotation.typing()); } /** * Creates a new offset mapping for an array containing all arguments. * * @param target The component target type. * @param readOnly {@code true} if the array is read-only. * @param typing The typing to apply. */ public ForAllArguments(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) { this.target = target; this.readOnly = readOnly; this.typing = typing; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { List valueReads = new ArrayList(instrumentedMethod.getParameters().size()); for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) { StackManipulation readAssignment = assigner.assign(parameterDescription.getType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target); } valueReads.add(new StackManipulation.Compound(MethodVariableAccess.load(parameterDescription), readAssignment)); } if (readOnly) { return new Target.ForArray.ReadOnly(target, valueReads); } else { List valueWrites = new ArrayList(instrumentedMethod.getParameters().size()); for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) { StackManipulation writeAssignment = assigner.assign(target, parameterDescription.getType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + parameterDescription); } valueWrites.add(new StackManipulation.Compound(writeAssignment, MethodVariableAccess.store(parameterDescription))); } return new Target.ForArray.ReadWrite(target, valueReads, valueWrites); } } /** * A factory for an offset mapping that maps all arguments values of the instrumented method. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public Class getAnnotationType() { return AllArguments.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().represents(Object.class) && !target.getType().isArray()) { throw new IllegalStateException("Cannot use AllArguments annotation on a non-array type"); } else if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) { throw new IllegalStateException("Cannot define writable field access for " + target); } else { return new ForAllArguments(target.getType().represents(Object.class) ? TypeDescription.Generic.OBJECT : target.getType().getComponentType(), annotation.loadSilent()); } } } } /** * Maps the declaring type of the instrumented method. */ enum ForInstrumentedType implements OffsetMapping { /** * The singleton instance. */ INSTANCE; @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { return Target.ForStackManipulation.of(instrumentedType); } } /** * Maps a constant representing the instrumented method. */ enum ForInstrumentedMethod implements OffsetMapping { /** * A constant that must be a {@link Method} instance. */ METHOD { @Override protected boolean isRepresentable(MethodDescription instrumentedMethod) { return instrumentedMethod.isMethod(); } }, /** * A constant that must be a {@link Constructor} instance. */ CONSTRUCTOR { @Override protected boolean isRepresentable(MethodDescription instrumentedMethod) { return instrumentedMethod.isConstructor(); } }, /** * A constant that must be a {@code java.lang.reflect.Executable} instance. */ EXECUTABLE { @Override protected boolean isRepresentable(MethodDescription instrumentedMethod) { return true; } }; @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { if (!isRepresentable(instrumentedMethod)) { throw new IllegalStateException("Cannot represent " + instrumentedMethod + " as given method constant"); } return Target.ForStackManipulation.of(instrumentedMethod.asDefined()); } /** * Checks if the supplied method is representable for the assigned offset mapping. * * @param instrumentedMethod The instrumented method to represent. * @return {@code true} if this method is representable. */ protected abstract boolean isRepresentable(MethodDescription instrumentedMethod); } /** * An offset mapping for a field. */ @EqualsAndHashCode abstract class ForField implements OffsetMapping { /** * The {@link FieldValue#value()} method. */ private static final MethodDescription.InDefinedShape VALUE; /** * The {@link FieldValue#declaringType()}} method. */ private static final MethodDescription.InDefinedShape DECLARING_TYPE; /** * The {@link FieldValue#readOnly()}} method. */ private static final MethodDescription.InDefinedShape READ_ONLY; /** * The {@link FieldValue#typing()}} method. */ private static final MethodDescription.InDefinedShape TYPING; /* * Looks up all annotation properties to avoid loading of the declaring field type. */ static { MethodList methods = new TypeDescription.ForLoadedType(FieldValue.class).getDeclaredMethods(); VALUE = methods.filter(named("value")).getOnly(); DECLARING_TYPE = methods.filter(named("declaringType")).getOnly(); READ_ONLY = methods.filter(named("readOnly")).getOnly(); TYPING = methods.filter(named("typing")).getOnly(); } /** * The expected type that the field can be assigned to. */ private final TypeDescription.Generic target; /** * {@code true} if this mapping is read-only. */ private final boolean readOnly; /** * The typing to apply. */ private final Assigner.Typing typing; /** * Creates an offset mapping for a field. * * @param target The target type. * @param readOnly {@code true} if this mapping is read-only. * @param typing The typing to apply. */ public ForField(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) { this.target = target; this.readOnly = readOnly; this.typing = typing; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { FieldDescription fieldDescription = resolve(instrumentedType); if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) { throw new IllegalStateException("Cannot read non-static field " + fieldDescription + " from static method " + instrumentedMethod); } else if (!context.isInitialized() && !fieldDescription.isStatic()) { throw new IllegalStateException("Cannot access non-static field before calling constructor: " + instrumentedMethod); } StackManipulation readAssignment = assigner.assign(fieldDescription.getType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + fieldDescription + " to " + target); } else if (readOnly) { return new Target.ForField.ReadOnly(fieldDescription, readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, fieldDescription.getType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + fieldDescription); } return new Target.ForField.ReadWrite(fieldDescription.asDefined(), readAssignment, writeAssignment); } } /** * Resolves the field being bound. * * @param instrumentedType The instrumented type. * @return The field being bound. */ protected abstract FieldDescription resolve(TypeDescription instrumentedType); /** * An offset mapping for a field that is resolved from the instrumented type by its name. */ @EqualsAndHashCode(callSuper = true) public abstract static class Unresolved extends ForField { /** * The name of the field. */ private final String name; /** * Creates an offset mapping for a field that is not yet resolved. * * @param target The target type. * @param readOnly {@code true} if this mapping is read-only. * @param typing The typing to apply. * @param name The name of the field. */ public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name) { super(target, readOnly, typing); this.name = name; } @Override protected FieldDescription resolve(TypeDescription instrumentedType) { FieldLocator.Resolution resolution = fieldLocator(instrumentedType).locate(name); if (!resolution.isResolved()) { throw new IllegalStateException("Cannot locate field named " + name + " for " + instrumentedType); } else { return resolution.getField(); } } /** * Returns a field locator for this instance. * * @param instrumentedType The instrumented type. * @return An appropriate field locator. */ protected abstract FieldLocator fieldLocator(TypeDescription instrumentedType); /** * An offset mapping for a field with an implicit declaring type. */ public static class WithImplicitType extends Unresolved { /** * Creates an offset mapping for a field with an implicit declaring type. * * @param target The target type. * @param annotation The annotation to represent. */ protected WithImplicitType(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) { this(target, annotation.getValue(READ_ONLY).resolve(Boolean.class), annotation.getValue(TYPING).loadSilent(Assigner.Typing.class.getClassLoader()).resolve(Assigner.Typing.class), annotation.getValue(VALUE).resolve(String.class)); } /** * Creates an offset mapping for a field with an implicit declaring type. * * @param target The target type. * @param name The name of the field. * @param readOnly {@code true} if the field is read-only. * @param typing The typing to apply. */ public WithImplicitType(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name) { super(target, readOnly, typing, name); } @Override protected FieldLocator fieldLocator(TypeDescription instrumentedType) { return new FieldLocator.ForClassHierarchy(instrumentedType); } } /** * An offset mapping for a field with an explicit declaring type. */ @EqualsAndHashCode(callSuper = true) public static class WithExplicitType extends Unresolved { /** * The type declaring the field. */ private final TypeDescription declaringType; /** * Creates an offset mapping for a field with an explicit declaring type. * * @param target The target type. * @param annotation The annotation to represent. * @param declaringType The field's declaring type. */ protected WithExplicitType(TypeDescription.Generic target, AnnotationDescription.Loadable annotation, TypeDescription declaringType) { this(target, annotation.getValue(READ_ONLY).resolve(Boolean.class), annotation.getValue(TYPING).loadSilent(Assigner.Typing.class.getClassLoader()).resolve(Assigner.Typing.class), annotation.getValue(VALUE).resolve(String.class), declaringType); } /** * Creates an offset mapping for a field with an explicit declaring type. * * @param target The target type. * @param name The name of the field. * @param readOnly {@code true} if the field is read-only. * @param typing The typing to apply. * @param declaringType The field's declaring type. */ public WithExplicitType(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name, TypeDescription declaringType) { super(target, readOnly, typing, name); this.declaringType = declaringType; } @Override protected FieldLocator fieldLocator(TypeDescription instrumentedType) { if (!declaringType.represents(TargetType.class) && !instrumentedType.isAssignableTo(declaringType)) { throw new IllegalStateException(declaringType + " is no super type of " + instrumentedType); } return new FieldLocator.ForExactType(TargetType.resolve(declaringType, instrumentedType)); } } /** * A factory for a {@link Unresolved} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public Class getAnnotationType() { return FieldValue.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(ForField.READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot write to field for " + target + " in read-only context"); } else { TypeDescription declaringType = annotation.getValue(DECLARING_TYPE).resolve(TypeDescription.class); return declaringType.represents(void.class) ? new WithImplicitType(target.getType(), annotation) : new WithExplicitType(target.getType(), annotation, declaringType); } } } } /** * A binding for an offset mapping that represents a specific field. */ @EqualsAndHashCode(callSuper = true) public static class Resolved extends ForField { /** * The accessed field. */ private final FieldDescription fieldDescription; /** * Creates a resolved offset mapping for a field. * * @param target The target type. * @param readOnly {@code true} if this mapping is read-only. * @param typing The typing to apply. * @param fieldDescription The accessed field. */ public Resolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, FieldDescription fieldDescription) { super(target, readOnly, typing); this.fieldDescription = fieldDescription; } @Override protected FieldDescription resolve(TypeDescription instrumentedType) { if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) { throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType); } else if (!fieldDescription.isAccessibleTo(instrumentedType)) { throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType); } return fieldDescription; } /** * A factory that binds a field. * * @param The annotation type this factory binds. */ @EqualsAndHashCode public static class Factory implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * The field to be bound. */ private final FieldDescription fieldDescription; /** * {@code true} if this factory should create a read-only binding. */ private final boolean readOnly; /** * The typing to use. */ private final Assigner.Typing typing; /** * Creates a new factory for binding a specific field with read-only semantics and static typing. * * @param annotationType The annotation type. * @param fieldDescription The field to bind. */ public Factory(Class annotationType, FieldDescription fieldDescription) { this(annotationType, fieldDescription, true, Assigner.Typing.STATIC); } /** * Creates a new factory for binding a specific field. * * @param annotationType The annotation type. * @param fieldDescription The field to bind. * @param readOnly {@code true} if this factory should create a read-only binding. * @param typing The typing to use. */ public Factory(Class annotationType, FieldDescription fieldDescription, boolean readOnly, Assigner.Typing typing) { this.annotationType = annotationType; this.fieldDescription = fieldDescription; this.readOnly = readOnly; this.typing = typing; } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new Resolved(target.getType(), readOnly, typing, fieldDescription); } } } } /** * An offset mapping for the {@link Advice.Origin} annotation. */ @EqualsAndHashCode class ForOrigin implements OffsetMapping { /** * The delimiter character. */ private static final char DELIMITER = '#'; /** * The escape character. */ private static final char ESCAPE = '\\'; /** * The renderers to apply. */ private final List renderers; /** * Creates a new offset mapping for an origin value. * * @param renderers The renderers to apply. */ public ForOrigin(List renderers) { this.renderers = renderers; } /** * Parses a pattern of an origin annotation. * * @param pattern The supplied pattern. * @return An appropriate offset mapping. */ public static OffsetMapping parse(String pattern) { if (pattern.equals(Origin.DEFAULT)) { return new ForOrigin(Collections.singletonList(Renderer.ForStringRepresentation.INSTANCE)); } else { List renderers = new ArrayList(pattern.length()); int from = 0; for (int to = pattern.indexOf(DELIMITER); to != -1; to = pattern.indexOf(DELIMITER, from)) { if (to != 0 && pattern.charAt(to - 1) == ESCAPE && (to == 1 || pattern.charAt(to - 2) != ESCAPE)) { renderers.add(new Renderer.ForConstantValue(pattern.substring(from, Math.max(0, to - 1)) + DELIMITER)); from = to + 1; continue; } else if (pattern.length() == to + 1) { throw new IllegalStateException("Missing sort descriptor for " + pattern + " at index " + to); } renderers.add(new Renderer.ForConstantValue(pattern.substring(from, to).replace("" + ESCAPE + ESCAPE, "" + ESCAPE))); switch (pattern.charAt(to + 1)) { case Renderer.ForMethodName.SYMBOL: renderers.add(Renderer.ForMethodName.INSTANCE); break; case Renderer.ForTypeName.SYMBOL: renderers.add(Renderer.ForTypeName.INSTANCE); break; case Renderer.ForDescriptor.SYMBOL: renderers.add(Renderer.ForDescriptor.INSTANCE); break; case Renderer.ForReturnTypeName.SYMBOL: renderers.add(Renderer.ForReturnTypeName.INSTANCE); break; case Renderer.ForJavaSignature.SYMBOL: renderers.add(Renderer.ForJavaSignature.INSTANCE); break; default: throw new IllegalStateException("Illegal sort descriptor " + pattern.charAt(to + 1) + " for " + pattern); } from = to + 2; } renderers.add(new Renderer.ForConstantValue(pattern.substring(from))); return new ForOrigin(renderers); } } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { StringBuilder stringBuilder = new StringBuilder(); for (Renderer renderer : renderers) { stringBuilder.append(renderer.apply(instrumentedType, instrumentedMethod)); } return Target.ForStackManipulation.of(stringBuilder.toString()); } /** * A renderer for an origin pattern element. */ public interface Renderer { /** * Returns a string representation for this renderer. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @return The string representation. */ String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod); /** * A renderer for a method's internal name. */ enum ForMethodName implements Renderer { /** * The singleton instance. */ INSTANCE; /** * The method name symbol. */ public static final char SYMBOL = 'm'; @Override public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return instrumentedMethod.getInternalName(); } } /** * A renderer for a method declaring type's binary name. */ enum ForTypeName implements Renderer { /** * The singleton instance. */ INSTANCE; /** * The type name symbol. */ public static final char SYMBOL = 't'; @Override public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return instrumentedType.getName(); } } /** * A renderer for a method descriptor. */ enum ForDescriptor implements Renderer { /** * The singleton instance. */ INSTANCE; /** * The descriptor symbol. */ public static final char SYMBOL = 'd'; @Override public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return instrumentedMethod.getDescriptor(); } } /** * A renderer for a method's Java signature in binary form. */ enum ForJavaSignature implements Renderer { /** * The singleton instance. */ INSTANCE; /** * The signature symbol. */ public static final char SYMBOL = 's'; @Override public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { StringBuilder stringBuilder = new StringBuilder("("); boolean comma = false; for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { if (comma) { stringBuilder.append(','); } else { comma = true; } stringBuilder.append(typeDescription.getName()); } return stringBuilder.append(')').toString(); } } /** * A renderer for a method's return type in binary form. */ enum ForReturnTypeName implements Renderer { /** * The singleton instance. */ INSTANCE; /** * The return type symbol. */ public static final char SYMBOL = 'r'; @Override public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return instrumentedMethod.getReturnType().asErasure().getName(); } } /** * A renderer for a method's {@link Object#toString()} representation. */ enum ForStringRepresentation implements Renderer { /** * The singleton instance. */ INSTANCE; @Override public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return instrumentedMethod.toString(); } } /** * A renderer for a constant value. */ @EqualsAndHashCode class ForConstantValue implements Renderer { /** * The constant value. */ private final String value; /** * Creates a new renderer for a constant value. * * @param value The constant value. */ public ForConstantValue(String value) { this.value = value; } @Override public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return value; } } } /** * A factory for a method origin. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public Class getAnnotationType() { return Origin.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (target.getType().asErasure().represents(Class.class)) { return OffsetMapping.ForInstrumentedType.INSTANCE; } else if (target.getType().asErasure().represents(Method.class)) { return OffsetMapping.ForInstrumentedMethod.METHOD; } else if (target.getType().asErasure().represents(Constructor.class)) { return OffsetMapping.ForInstrumentedMethod.CONSTRUCTOR; } else if (JavaType.EXECUTABLE.getTypeStub().equals(target.getType().asErasure())) { return OffsetMapping.ForInstrumentedMethod.EXECUTABLE; } else if (target.getType().asErasure().isAssignableFrom(String.class)) { return ForOrigin.parse(annotation.loadSilent().value()); } else { throw new IllegalStateException("Non-supported type " + target.getType() + " for @Origin annotation"); } } } } /** * An offset mapping for a parameter where assignments are fully ignored and that always return the parameter type's default value. */ @EqualsAndHashCode class ForUnusedValue implements OffsetMapping { /** * The unused type. */ private final TypeDefinition target; /** * Creates a new offset mapping for an unused type. * * @param target The unused type. */ public ForUnusedValue(TypeDefinition target) { this.target = target; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { return new Target.ForDefaultValue.ReadWrite(target); } /** * A factory for an offset mapping for an unused value. */ protected enum Factory implements OffsetMapping.Factory { /** * A factory for representing an unused value. */ INSTANCE; @Override public Class getAnnotationType() { return Unused.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new ForUnusedValue(target.getType()); } } } /** * An offset mapping for a parameter where assignments are fully ignored and that is assigned a boxed version of the instrumented * method's return valueor {@code null} if the return type is not primitive or {@code void}. */ enum ForStubValue implements OffsetMapping, Factory { /** * The singleton instance. */ INSTANCE; @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { return new Target.ForDefaultValue.ReadOnly(instrumentedMethod.getReturnType(), assigner.assign(instrumentedMethod.getReturnType(), TypeDescription.Generic.OBJECT, Assigner.Typing.DYNAMIC)); } @Override public Class getAnnotationType() { return StubValue.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().represents(Object.class)) { throw new IllegalStateException("Cannot use StubValue on non-Object parameter type " + target); } else { return this; } } } /** * An offset mapping that provides access to the value that is returned by the enter advice. */ @EqualsAndHashCode class ForEnterValue implements OffsetMapping { /** * The represented target type. */ private final TypeDescription.Generic target; /** * The enter type. */ private final TypeDescription.Generic enterType; /** * {@code true} if the annotated value is read-only. */ private final boolean readOnly; /** * The typing to apply. */ private final Assigner.Typing typing; /** * Creates a new offset mapping for the enter type. * * @param target The represented target type. * @param enterType The enter type. * @param enter The represented annotation. */ protected ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, Enter enter) { this(target, enterType, enter.readOnly(), enter.typing()); } /** * Creates a new offset mapping for the enter type. * * @param target The represented target type. * @param enterType The enter type. * @param readOnly {@code true} if the annotated value is read-only. * @param typing The typing to apply. */ public ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, boolean readOnly, Assigner.Typing typing) { this.target = target; this.enterType = enterType; this.readOnly = readOnly; this.typing = typing; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { StackManipulation readAssignment = assigner.assign(enterType, target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + enterType + " to " + target); } else if (readOnly) { return new Target.ForVariable.ReadOnly(target, instrumentedMethod.getStackSize(), readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, enterType, typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + enterType); } return new Target.ForVariable.ReadWrite(target, instrumentedMethod.getStackSize(), readAssignment, writeAssignment); } } /** * A factory for creating a {@link ForEnterValue} offset mapping. */ @EqualsAndHashCode protected static class Factory implements OffsetMapping.Factory { /** * The supplied type of the enter method. */ private final TypeDefinition enterType; /** * Creates a new factory for creating a {@link ForEnterValue} offset mapping. * * @param enterType The supplied type of the enter method. */ protected Factory(TypeDefinition enterType) { this.enterType = enterType; } @Override public Class getAnnotationType() { return Enter.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { return new ForEnterValue(target.getType(), enterType.asGenericType(), annotation.loadSilent()); } } } } /** * An offset mapping that provides access to the value that is returned by the instrumented method. */ @EqualsAndHashCode class ForReturnValue implements OffsetMapping { /** * The type that the advice method expects for the return value. */ private final TypeDescription.Generic target; /** * Determines if the parameter is to be treated as read-only. */ private final boolean readOnly; /** * The typing to apply. */ private final Assigner.Typing typing; /** * Creates a new offset mapping for a return value. * * @param target The type that the advice method expects for the return value. * @param annotation The annotation being bound. */ protected ForReturnValue(TypeDescription.Generic target, Return annotation) { this(target, annotation.readOnly(), annotation.typing()); } /** * Creates a new offset mapping for a return value. * * @param target The type that the advice method expects for the return value. * @param readOnly Determines if the parameter is to be treated as read-only. * @param typing The typing to apply. */ public ForReturnValue(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) { this.target = target; this.readOnly = readOnly; this.typing = typing; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { int offset = instrumentedMethod.getStackSize() + context.getPadding(); StackManipulation readAssignment = assigner.assign(instrumentedMethod.getReturnType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + instrumentedMethod.getReturnType() + " to " + target); } else if (readOnly) { return instrumentedMethod.getReturnType().represents(void.class) ? new Target.ForDefaultValue.ReadOnly(target) : new Target.ForVariable.ReadOnly(instrumentedMethod.getReturnType(), offset, readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, instrumentedMethod.getReturnType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + instrumentedMethod.getReturnType()); } return instrumentedMethod.getReturnType().represents(void.class) ? new Target.ForDefaultValue.ReadWrite(target) : new Target.ForVariable.ReadWrite(instrumentedMethod.getReturnType(), offset, readAssignment, writeAssignment); } } /** * A factory for creating a {@link ForReturnValue} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singelton instance. */ INSTANCE; @Override public Class getAnnotationType() { return Return.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) { throw new IllegalStateException("Cannot write return value for " + target + " in read-only context"); } else { return new ForReturnValue(target.getType(), annotation.loadSilent()); } } } } /** * An offset mapping for accessing a {@link Throwable} of the instrumented method. */ @EqualsAndHashCode class ForThrowable implements OffsetMapping { /** * The type of parameter that is being accessed. */ private final TypeDescription.Generic target; /** * {@code true} if the parameter is read-only. */ private final boolean readOnly; /** * The typing to apply. */ private final Assigner.Typing typing; /** * Creates a new offset mapping for access of the exception that is thrown by the instrumented method.. * * @param target The type of parameter that is being accessed. * @param annotation The annotation to bind. */ protected ForThrowable(TypeDescription.Generic target, Thrown annotation) { this(target, annotation.readOnly(), annotation.typing()); } /** * Creates a new offset mapping for access of the exception that is thrown by the instrumented method.. * * @param target The type of parameter that is being accessed. * @param readOnly {@code true} if the parameter is read-only. * @param typing The typing to apply. */ public ForThrowable(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) { this.target = target; this.readOnly = readOnly; this.typing = typing; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { int offset = instrumentedMethod.getStackSize() + context.getPadding() + instrumentedMethod.getReturnType().getStackSize().getSize(); StackManipulation readAssignment = assigner.assign(TypeDescription.THROWABLE.asGenericType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign Throwable to " + target); } else if (readOnly) { return new Target.ForVariable.ReadOnly(TypeDescription.THROWABLE, offset, readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, TypeDescription.THROWABLE.asGenericType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to Throwable"); } return new Target.ForVariable.ReadWrite(TypeDescription.THROWABLE, offset, readAssignment, writeAssignment); } } /** * A factory for accessing an exception that was thrown by the instrumented method. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * Resolves an appropriate offset mapping factory for the {@link Thrown} parameter annotation. * * @param adviceMethod The exit advice method, annotated with {@link OnMethodExit}. * @return An appropriate offset mapping factory. */ @SuppressWarnings("unchecked") // In absence of @SafeVarargs for Java 6 protected static OffsetMapping.Factory of(MethodDescription.InDefinedShape adviceMethod) { return adviceMethod.getDeclaredAnnotations() .ofType(OnMethodExit.class) .getValue(ON_THROWABLE) .resolve(TypeDescription.class) .represents(NoExceptionHandler.class) ? new OffsetMapping.Factory.Illegal(Thrown.class) : Factory.INSTANCE; } @Override public Class getAnnotationType() { return Thrown.class; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.loadSilent().readOnly()) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { return new ForThrowable(target.getType(), annotation.loadSilent()); } } } } /** * An offset mapping for binding a stack manipulation. */ @EqualsAndHashCode class ForStackManipulation implements OffsetMapping { /** * The stack manipulation that loads the bound value. */ private final StackManipulation stackManipulation; /** * The type of the loaded value. */ private final TypeDescription.Generic typeDescription; /** * The target type of the annotated parameter. */ private final TypeDescription.Generic targetType; /** * The typing to apply. */ private final Assigner.Typing typing; /** * Creates an offset mapping that binds a stack manipulation. * * @param stackManipulation The stack manipulation that loads the bound value. * @param typeDescription The type of the loaded value. * @param targetType The target type of the annotated parameter. * @param typing The typing to apply. */ public ForStackManipulation(StackManipulation stackManipulation, TypeDescription.Generic typeDescription, TypeDescription.Generic targetType, Assigner.Typing typing) { this.stackManipulation = stackManipulation; this.typeDescription = typeDescription; this.targetType = targetType; this.typing = typing; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { StackManipulation assigment = assigner.assign(typeDescription, targetType, typing); if (!assigment.isValid()) { throw new IllegalStateException("Cannot assign " + typeDescription + " to " + targetType); } return new Target.ForStackManipulation(new StackManipulation.Compound(stackManipulation, assigment)); } /** * A factory that binds a stack manipulation. * * @param The annotation type this factory binds. */ @EqualsAndHashCode public static class Factory implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * The stack manipulation that loads the bound value. */ private final StackManipulation stackManipulation; /** * The type of the loaded value. */ private final TypeDescription.Generic typeDescription; /** * Creates a new factory for binding a type description. * * @param annotationType The annotation type. * @param typeDescription The type to bind. */ public Factory(Class annotationType, TypeDescription typeDescription) { this(annotationType, ClassConstant.of(typeDescription), TypeDescription.CLASS.asGenericType()); } /** * Creates a new factory for binding an enumeration. * * @param annotationType The annotation type. * @param enumerationDescription The enumeration to bind. */ public Factory(Class annotationType, EnumerationDescription enumerationDescription) { this(annotationType, FieldAccess.forEnumeration(enumerationDescription), enumerationDescription.getEnumerationType().asGenericType()); } /** * Creates a new factory for binding a stack manipulation. * * @param annotationType The annotation type. * @param stackManipulation The stack manipulation that loads the bound value. * @param typeDescription The type of the loaded value. */ public Factory(Class annotationType, StackManipulation stackManipulation, TypeDescription.Generic typeDescription) { this.annotationType = annotationType; this.stackManipulation = stackManipulation; this.typeDescription = typeDescription; } /** * Creates a binding for a fixed {@link String} or primitive value. * * @param annotationType The annotation type. * @param value The primitive (wrapper) value or {@link String} value to bind. * @param The annotation type. * @return A factory for creating an offset mapping that binds the supplied value. */ public static OffsetMapping.Factory of(Class annotationType, Object value) { StackManipulation stackManipulation; TypeDescription typeDescription; if (value == null) { return new OfDefaultValue(annotationType); } else if (value instanceof Boolean) { stackManipulation = IntegerConstant.forValue((Boolean) value); typeDescription = new TypeDescription.ForLoadedType(boolean.class); } else if (value instanceof Byte) { stackManipulation = IntegerConstant.forValue((Byte) value); typeDescription = new TypeDescription.ForLoadedType(byte.class); } else if (value instanceof Short) { stackManipulation = IntegerConstant.forValue((Short) value); typeDescription = new TypeDescription.ForLoadedType(short.class); } else if (value instanceof Character) { stackManipulation = IntegerConstant.forValue((Character) value); typeDescription = new TypeDescription.ForLoadedType(char.class); } else if (value instanceof Integer) { stackManipulation = IntegerConstant.forValue((Integer) value); typeDescription = new TypeDescription.ForLoadedType(int.class); } else if (value instanceof Long) { stackManipulation = LongConstant.forValue((Long) value); typeDescription = new TypeDescription.ForLoadedType(long.class); } else if (value instanceof Float) { stackManipulation = FloatConstant.forValue((Float) value); typeDescription = new TypeDescription.ForLoadedType(float.class); } else if (value instanceof Double) { stackManipulation = DoubleConstant.forValue((Double) value); typeDescription = new TypeDescription.ForLoadedType(double.class); } else if (value instanceof String) { stackManipulation = new TextConstant((String) value); typeDescription = TypeDescription.STRING; } else { throw new IllegalStateException("Not a constant value: " + value); } return new Factory(annotationType, stackManipulation, typeDescription.asGenericType()); } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new ForStackManipulation(stackManipulation, typeDescription, target.getType(), Assigner.Typing.STATIC); } } /** * A factory for binding the annotated parameter's default value. * * @param The annotation type this factory binds. */ @EqualsAndHashCode public static class OfDefaultValue implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * Creates a factory for an offset mapping tat binds the parameter's default value. * * @param annotationType The annotation type. */ public OfDefaultValue(Class annotationType) { this.annotationType = annotationType; } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new ForStackManipulation(DefaultValue.of(target.getType()), target.getType(), target.getType(), Assigner.Typing.STATIC); } } /** * A factory for binding an annotation's property. * * @param The annotation type this factory binds. */ @EqualsAndHashCode public static class OfAnnotationProperty implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * The annotation property. */ private final MethodDescription.InDefinedShape property; /** * Creates a factory for binding an annotation property. * * @param annotationType The annotation type. * @param property The annotation property. */ protected OfAnnotationProperty(Class annotationType, MethodDescription.InDefinedShape property) { this.annotationType = annotationType; this.property = property; } /** * Creates a factory for an offset mapping that binds an annotation property. * * @param annotationType The annotion type to bind. * @param property The property to bind. * @param The annotation type. * @return A factory for binding a property of the annotation type. */ public static OffsetMapping.Factory of(Class annotationType, String property) { if (!annotationType.isAnnotation()) { throw new IllegalArgumentException("Not an annotation type: " + annotationType); } try { return new OfAnnotationProperty(annotationType, new MethodDescription.ForLoadedMethod(annotationType.getMethod(property))); } catch (NoSuchMethodException exception) { throw new IllegalArgumentException("Cannot find a property " + property + " on " + annotationType, exception); } } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { Object value = annotation.getValue(property).resolve(); OffsetMapping.Factory factory; if (value instanceof TypeDescription) { factory = new Factory(annotationType, (TypeDescription) value); } else if (value instanceof EnumerationDescription) { factory = new Factory(annotationType, (EnumerationDescription) value); } else if (value instanceof AnnotationDescription) { throw new IllegalStateException("Cannot bind annotation as fixed value for " + property); } else { factory = Factory.of(annotationType, value); } return factory.make(target, annotation, adviceType); } } } /** * An offset mapping that loads a serialized value. */ @EqualsAndHashCode class ForSerializedValue implements OffsetMapping { /** * The type of the serialized value as it is used. */ private final TypeDescription.Generic target; /** * The class type of the serialized value. */ private final TypeDescription typeDescription; /** * The stack manipulation deserializing the represented value. */ private final StackManipulation deserialization; /** * Creates a new offset mapping for a serialized value. * * @param target The type of the serialized value as it is used. * @param typeDescription The class type of the serialized value. * @param deserialization The stack manipulation deserializing the represented value. */ public ForSerializedValue(TypeDescription.Generic target, TypeDescription typeDescription, StackManipulation deserialization) { this.target = target; this.typeDescription = typeDescription; this.deserialization = deserialization; } @Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Context context) { StackManipulation assignment = assigner.assign(typeDescription.asGenericType(), target, Assigner.Typing.DYNAMIC); if (!assignment.isValid()) { throw new IllegalStateException("Cannot assign " + typeDescription + " to " + target); } return new Target.ForStackManipulation(new StackManipulation.Compound(deserialization, assignment)); } /** * A factory for loading a deserialized value. * * @param The annotation type this factory binds. */ @EqualsAndHashCode public static class Factory implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * The type description as which to treat the deserialized value. */ private final TypeDescription typeDescription; /** * The stack manipulation that loads the represented value. */ private final StackManipulation deserialization; /** * Creates a factory for loading a deserialized value. * * @param annotationType The annotation type. * @param typeDescription The type description as which to treat the deserialized value. * @param deserialization The stack manipulation that loads the represented value. */ protected Factory(Class annotationType, TypeDescription typeDescription, StackManipulation deserialization) { this.annotationType = annotationType; this.typeDescription = typeDescription; this.deserialization = deserialization; } /** * Creates a factory for an offset mapping that loads the provided value. * * @param annotationType The annotation type to be bound. * @param target The instance representing the value to be deserialized. * @param targetType The target type as which to use the target value. * @param The annotation type the created factory binds. * @return An appropriate offset mapping factory. */ public static OffsetMapping.Factory of(Class annotationType, Serializable target, Class targetType) { if (!targetType.isInstance(target)) { throw new IllegalArgumentException(target + " is no instance of " + targetType); } return new Factory(annotationType, new TypeDescription.ForLoadedType(targetType), SerializedConstant.of(target)); } @Override public Class getAnnotationType() { return annotationType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new ForSerializedValue(target.getType(), typeDescription, deserialization); } } } } /** * A handler for computing the instrumented method's size. */ protected interface MethodSizeHandler { /** * Indicates that a size is not computed but handled directly by ASM. */ int UNDEFINED_SIZE = Short.MAX_VALUE; /** * Requires a minimum length of the local variable array. * * @param localVariableLength The minimal required length of the local variable array. */ void requireLocalVariableLength(int localVariableLength); /** * A method size handler for the instrumented method. */ interface ForInstrumentedMethod extends MethodSizeHandler { /** * Binds a method size handler for the entry advice. * * @param adviceMethod The method representing the entry advice. * @return A method size handler for the entry advice. */ ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod); /** * Binds the method size handler for the exit advice. * * @param adviceMethod The method representing the exit advice. * @param skipThrowable {@code true} if the exit advice is not invoked on an exception. * @return A method size handler for the exit advice. */ ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod, boolean skipThrowable); /** * Computes a compound stack size for the advice and the translated instrumented method. * * @param stackSize The required stack size of the instrumented method before translation. * @return The stack size required by the instrumented method and its advice methods. */ int compoundStackSize(int stackSize); /** * Computes a compound local variable array length for the advice and the translated instrumented method. * * @param localVariableLength The required local variable array length of the instrumented method before translation. * @return The local variable length required by the instrumented method and its advice methods. */ int compoundLocalVariableLength(int localVariableLength); } /** * A method size handler for an advice method. */ interface ForAdvice extends MethodSizeHandler { /** * Records a minimum stack size required by the represented advice method. * * @param stackSize The minimum size required by the represented advice method. */ void requireStackSize(int stackSize); /** * Records the maximum values for stack size and local variable array which are required by the advice method * for its individual execution without translation. * * @param stackSize The minimum required stack size. * @param localVariableLength The minimum required length of the local variable array. */ void recordMaxima(int stackSize, int localVariableLength); /** * Records a minimum padding additionally to the computed stack size that is required for implementing this advice method. * * @param padding The minimum required padding. */ void recordPadding(int padding); } /** * A non-operational method size handler. */ enum NoOp implements ForInstrumentedMethod, ForAdvice { /** * The singleton instance. */ INSTANCE; @Override public ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) { return this; } @Override public ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod, boolean skipThrowable) { return this; } @Override public int compoundStackSize(int stackSize) { return UNDEFINED_SIZE; } @Override public int compoundLocalVariableLength(int localVariableLength) { return UNDEFINED_SIZE; } @Override public void requireLocalVariableLength(int localVariableLength) { /* do nothing */ } @Override public void requireStackSize(int stackSize) { /* do nothing */ } @Override public void recordMaxima(int stackSize, int localVariableLength) { /* do nothing */ } @Override public void recordPadding(int padding) { /* do nothing */ } } /** * A default implementation for a method size handler. */ class Default implements MethodSizeHandler.ForInstrumentedMethod { /** * The instrumented method. */ private final MethodDescription instrumentedMethod; /** * The list of types that the instrumented method requires in addition to the method parameters. */ private final TypeList requiredTypes; /** * A list of types that are yielded by the instrumented method and available to the exit advice. */ private final TypeList yieldedTypes; /** * The maximum stack size required by a visited advice method. */ private int stackSize; /** * The maximum length of the local variable array required by a visited advice method. */ private int localVariableLength; /** * Creates a new default meta data handler that recomputes the space requirements of an instrumented method. * * @param instrumentedMethod The instrumented method. * @param requiredTypes The types this meta data handler expects to be available additionally to the instrumented method's parameters. * @param yieldedTypes The types that are expected to be added after the instrumented method returns. */ protected Default(MethodDescription instrumentedMethod, TypeList requiredTypes, TypeList yieldedTypes) { this.instrumentedMethod = instrumentedMethod; this.requiredTypes = requiredTypes; this.yieldedTypes = yieldedTypes; } /** * Creates a method size handler applicable for the given instrumented method. * * @param instrumentedMethod The instrumented method. * @param requiredTypes The list of types that the instrumented method requires in addition to the method parameters. * @param yieldedTypes A list of types that are yielded by the instrumented method and available to the exit advice. * @param writerFlags The flags supplied to the ASM class writer. * @return An appropriate method size handler. */ protected static MethodSizeHandler.ForInstrumentedMethod of(MethodDescription instrumentedMethod, List requiredTypes, List yieldedTypes, int writerFlags) { return (writerFlags & (ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES)) != 0 ? NoOp.INSTANCE : new Default(instrumentedMethod, new TypeList.Explicit(requiredTypes), new TypeList.Explicit(yieldedTypes)); } @Override public MethodSizeHandler.ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) { stackSize = Math.max(stackSize, adviceMethod.getReturnType().getStackSize().getSize()); return new ForAdvice(adviceMethod, new TypeList.Empty(), new TypeList.Explicit(requiredTypes)); } @Override public MethodSizeHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod, boolean skipThrowable) { stackSize = Math.max(stackSize, adviceMethod.getReturnType().getStackSize().maximum(skipThrowable ? StackSize.ZERO : StackSize.SINGLE).getSize()); return new ForAdvice(adviceMethod, new TypeList.Explicit(CompoundList.of(requiredTypes, yieldedTypes)), new TypeList.Empty()); } @Override public int compoundStackSize(int stackSize) { return Math.max(this.stackSize, stackSize); } @Override public int compoundLocalVariableLength(int localVariableLength) { return Math.max(this.localVariableLength, localVariableLength + requiredTypes.getStackSize() + yieldedTypes.getStackSize()); } @Override public void requireLocalVariableLength(int localVariableLength) { this.localVariableLength = Math.max(this.localVariableLength, localVariableLength); } /** * A method size handler for an advice method. */ protected class ForAdvice implements MethodSizeHandler.ForAdvice { /** * The advice method. */ private final MethodDescription.InDefinedShape adviceMethod; /** * A list of types required by this advice method. */ private final TypeList requiredTypes; /** * A list of types yielded by this advice method. */ private final TypeList yieldedTypes; /** * The padding that this advice method requires additionally to its computed size. */ private int padding; /** * Creates a new method size handler for an advice method. * * @param adviceMethod The advice method. * @param requiredTypes A list of types required by this advice method. * @param yieldedTypes A list of types yielded by this advice method. */ protected ForAdvice(MethodDescription.InDefinedShape adviceMethod, TypeList requiredTypes, TypeList yieldedTypes) { this.adviceMethod = adviceMethod; this.requiredTypes = requiredTypes; this.yieldedTypes = yieldedTypes; stackSize = Math.max(stackSize, adviceMethod.getReturnType().getStackSize().getSize()); } @Override public void requireLocalVariableLength(int localVariableLength) { Default.this.requireLocalVariableLength(localVariableLength); } @Override public void requireStackSize(int stackSize) { Default.this.stackSize = Math.max(Default.this.stackSize, stackSize); } @Override public void recordMaxima(int stackSize, int localVariableLength) { Default.this.stackSize = Math.max(Default.this.stackSize, stackSize) + padding; Default.this.localVariableLength = Math.max(Default.this.localVariableLength, localVariableLength - adviceMethod.getStackSize() + instrumentedMethod.getStackSize() + requiredTypes.getStackSize() + yieldedTypes.getStackSize()); } @Override public void recordPadding(int padding) { this.padding = Math.max(this.padding, padding); } } } } /** * A handler for computing and translating stack map frames. */ protected interface StackMapFrameHandler { /** * Translates a frame. * * @param methodVisitor The method visitor to write the frame to. * @param type The frame's type. * @param localVariableLength The local variable length. * @param localVariable An array containing the types of the current local variables. * @param stackSize The size of the operand stack. * @param stack An array containing the types of the current operand stack. */ void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack); /** * Injects a frame indicating the beginning of a return value handler for the currently handled method. * * @param methodVisitor The method visitor onto which to apply the stack map frame. */ void injectReturnFrame(MethodVisitor methodVisitor); /** * Injects a frame indicating the beginning of an exception handler for the currently handled method. * * @param methodVisitor The method visitor onto which to apply the stack map frame. */ void injectExceptionFrame(MethodVisitor methodVisitor); /** * Injects a frame indicating the completion of the currently handled method, i.e. all yielded types were added. * * @param methodVisitor The method visitor onto which to apply the stack map frame. * @param secondary {@code true} if another completion frame for this method was written previously. */ void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary); /** * A stack map frame handler for an instrumented method. */ interface ForInstrumentedMethod extends StackMapFrameHandler { /** * Binds this meta data handler for the entry advice. * * @param adviceMethod The entry advice method. * @return An appropriate meta data handler for the enter method. */ ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod); /** * Binds this meta data handler for the exit advice. * * @param adviceMethod The exit advice method. * @return An appropriate meta data handler for the enter method. */ ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod); /** * Returns a hint to supply to a {@link ClassReader} when parsing an advice method. * * @return The reader hint to supply to an ASM class reader. */ int getReaderHint(); } /** * A stack map frame handler for an advice method. */ interface ForAdvice extends StackMapFrameHandler { /* marker interface */ } /** * A non-operational stack map frame handler. */ enum NoOp implements ForInstrumentedMethod, ForAdvice { /** * The singleton instance. */ INSTANCE; @Override public StackMapFrameHandler.ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) { return this; } @Override public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { return this; } @Override public int getReaderHint() { return ClassReader.SKIP_FRAMES; } @Override public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) { /* do nothing */ } @Override public void injectReturnFrame(MethodVisitor methodVisitor) { /* do nothing */ } @Override public void injectExceptionFrame(MethodVisitor methodVisitor) { /* do nothing */ } @Override public void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary) { /* do nothing */ } } /** * A default implementation of a stack map frame handler for an instrumented method. */ class Default implements ForInstrumentedMethod { /** * An empty array indicating an empty frame. */ private static final Object[] EMPTY = new Object[0]; /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The instrumented method. */ protected final MethodDescription instrumentedMethod; /** * A list of intermediate types to be considered as part of the instrumented method's steady signature. */ protected final TypeList requiredTypes; /** * The types that are expected to be added after the instrumented method returns. */ protected final TypeList yieldedTypes; /** * {@code true} if the meta data handler is expected to expand its frames. */ private final boolean expandFrames; /** * The current frame's size divergence from the original local variable array. */ private int currentFrameDivergence; /** * Creates a new default meta data handler. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param requiredTypes A list of intermediate types to be considered as part of the instrumented method's steady signature. * @param yieldedTypes The types that are expected to be added after the instrumented method returns. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. */ protected Default(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypeList requiredTypes, TypeList yieldedTypes, boolean expandFrames) { this.instrumentedType = instrumentedType; this.instrumentedMethod = instrumentedMethod; this.requiredTypes = requiredTypes; this.yieldedTypes = yieldedTypes; this.expandFrames = expandFrames; } /** * Creates an appropriate stack map frame handler for an instrumented method. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param requiredTypes A list of intermediate types to be considered as part of the instrumented method's steady signature. * @param yieldedTypes The types that are expected to be added after the instrumented method returns. * @param classFileVersion The instrumented type's class file version. * @param writerFlags The flags supplied to the ASM writier. * @param readerFlags The reader flags supplied to the ASM reader. * @return An approrpiate stack map frame handler for an instrumented method. */ protected static ForInstrumentedMethod of(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List requiredTypes, List yieldedTypes, ClassFileVersion classFileVersion, int writerFlags, int readerFlags) { return (writerFlags & ClassWriter.COMPUTE_FRAMES) != 0 || classFileVersion.isLessThan(ClassFileVersion.JAVA_V6) ? NoOp.INSTANCE : new Default(instrumentedType, instrumentedMethod, new TypeList.Explicit(requiredTypes), new TypeList.Explicit(yieldedTypes), (readerFlags & ClassReader.EXPAND_FRAMES) != 0); } /** * Translates a type into a representation of its form inside a stack map frame. * * @param typeDescription The type to translate. * @return A stack entry representation of the supplied type. */ protected static Object toFrame(TypeDescription typeDescription) { if (typeDescription.represents(boolean.class) || typeDescription.represents(byte.class) || typeDescription.represents(short.class) || typeDescription.represents(char.class) || typeDescription.represents(int.class)) { return Opcodes.INTEGER; } else if (typeDescription.represents(long.class)) { return Opcodes.LONG; } else if (typeDescription.represents(float.class)) { return Opcodes.FLOAT; } else if (typeDescription.represents(double.class)) { return Opcodes.DOUBLE; } else { return typeDescription.getInternalName(); } } @Override public StackMapFrameHandler.ForAdvice bindEntry(MethodDescription.InDefinedShape adviceMethod) { return new ForAdvice(adviceMethod, new TypeList.Empty(), requiredTypes, TranslationMode.ENTRY); } @Override public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { return new ForAdvice(adviceMethod, new TypeList.Explicit(CompoundList.of(requiredTypes, yieldedTypes)), new TypeList.Empty(), TranslationMode.EXIT); } @Override public int getReaderHint() { return expandFrames ? ClassReader.EXPAND_FRAMES : AsmVisitorWrapper.NO_FLAGS; } @Override public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) { translateFrame(methodVisitor, TranslationMode.COPY, instrumentedMethod, requiredTypes, type, localVariableLength, localVariable, stackSize, stack); } /** * Translates a frame. * * @param methodVisitor The method visitor to write the frame to. * @param translationMode The translation mode to apply. * @param methodDescription The method description for which the frame is written. * @param additionalTypes The additional types to consider part of the instrumented method's parameters. * @param type The frame's type. * @param localVariableLength The local variable length. * @param localVariable An array containing the types of the current local variables. * @param stackSize The size of the operand stack. * @param stack An array containing the types of the current operand stack. */ protected void translateFrame(MethodVisitor methodVisitor, TranslationMode translationMode, MethodDescription methodDescription, TypeList additionalTypes, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) { switch (type) { case Opcodes.F_SAME: case Opcodes.F_SAME1: break; case Opcodes.F_APPEND: currentFrameDivergence += localVariableLength; break; case Opcodes.F_CHOP: currentFrameDivergence -= localVariableLength; break; case Opcodes.F_FULL: case Opcodes.F_NEW: if (methodDescription.getParameters().size() + (methodDescription.isStatic() ? 0 : 1) > localVariableLength) { throw new IllegalStateException("Inconsistent frame length for " + methodDescription + ": " + localVariableLength); } int offset; if (methodDescription.isStatic()) { offset = 0; } else { if (!translationMode.isPossibleThisFrameValue(instrumentedType, instrumentedMethod, localVariable[0])) { throw new IllegalStateException(methodDescription + " is inconsistent for 'this' reference: " + localVariable[0]); } offset = 1; } for (int index = 0; index < methodDescription.getParameters().size(); index++) { if (!toFrame(methodDescription.getParameters().get(index).getType().asErasure()).equals(localVariable[index + offset])) { throw new IllegalStateException(methodDescription + " is inconsistent at " + index + ": " + localVariable[index + offset]); } } Object[] translated = new Object[localVariableLength - methodDescription.getParameters().size() - (methodDescription.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size() + (instrumentedMethod.isStatic() ? 0 : 1) + additionalTypes.size()]; int index = translationMode.copy(instrumentedType, instrumentedMethod, methodDescription, localVariable, translated); for (TypeDescription typeDescription : additionalTypes) { translated[index++] = toFrame(typeDescription); } System.arraycopy(localVariable, methodDescription.getParameters().size() + (methodDescription.isStatic() ? 0 : 1), translated, index, translated.length - index); localVariableLength = translated.length; localVariable = translated; currentFrameDivergence = translated.length - index; break; default: throw new IllegalArgumentException("Unexpected frame type: " + type); } methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack); } @Override public void injectReturnFrame(MethodVisitor methodVisitor) { if (!expandFrames && currentFrameDivergence == 0 && !instrumentedMethod.isConstructor()) { if (instrumentedMethod.getReturnType().represents(void.class)) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{toFrame(instrumentedMethod.getReturnType().asErasure())}); } } else { injectFullFrame(methodVisitor, requiredTypes, instrumentedMethod.getReturnType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(instrumentedMethod.getReturnType().asErasure())); } } @Override public void injectExceptionFrame(MethodVisitor methodVisitor) { if (!expandFrames && currentFrameDivergence == 0) { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)}); } else { injectFullFrame(methodVisitor, requiredTypes, Collections.singletonList(TypeDescription.THROWABLE)); } } @Override public void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary) { if (!expandFrames && currentFrameDivergence == 0 && (secondary || !instrumentedMethod.isConstructor())) { if (secondary) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { Object[] local = new Object[yieldedTypes.size()]; int index = 0; for (TypeDescription typeDescription : yieldedTypes) { local[index++] = toFrame(typeDescription); } methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY); } } else { injectFullFrame(methodVisitor, CompoundList.of(requiredTypes, yieldedTypes), Collections.emptyList()); } } /** * Injects a full stack map frame. * * @param methodVisitor The method visitor onto which to write the stack map frame. * @param typesInArray The types that were added to the local variable array additionally to the values of the instrumented method. * @param typesOnStack The types currently on the operand stack. */ protected void injectFullFrame(MethodVisitor methodVisitor, List typesInArray, List typesOnStack) { Object[] localVariable = new Object[instrumentedMethod.getParameters().size() + (instrumentedMethod.isStatic() ? 0 : 1) + typesInArray.size()]; int index = 0; if (!instrumentedMethod.isStatic()) { localVariable[index++] = toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { localVariable[index++] = toFrame(typeDescription); } for (TypeDescription typeDescription : typesInArray) { localVariable[index++] = toFrame(typeDescription); } index = 0; Object[] stackType = new Object[typesOnStack.size()]; for (TypeDescription typeDescription : typesOnStack) { stackType[index++] = toFrame(typeDescription); } methodVisitor.visitFrame(expandFrames ? Opcodes.F_NEW : Opcodes.F_FULL, localVariable.length, localVariable, stackType.length, stackType); currentFrameDivergence = 0; } /** * A translation mode that determines how the fixed frames of the instrumented method are written. */ protected enum TranslationMode { /** * A translation mode that simply copies the original frames which are available when translating frames of the instrumented method. */ COPY { @Override protected int copy(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription methodDescription, Object[] localVariable, Object[] translated) { int length = instrumentedMethod.getParameters().size() + (instrumentedMethod.isStatic() ? 0 : 1); System.arraycopy(localVariable, 0, translated, 0, length); return length; } @Override protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) { return instrumentedMethod.isConstructor() && Opcodes.UNINITIALIZED_THIS.equals(frame) || toFrame(instrumentedType).equals(frame); } }, /** * A translation mode for the entry advice that considers that the {@code this} reference might not be initialized for a constructor. */ ENTRY { @Override protected int copy(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription methodDescription, Object[] localVariable, Object[] translated) { int index = 0; if (!instrumentedMethod.isStatic()) { translated[index++] = instrumentedMethod.isConstructor() ? Opcodes.UNINITIALIZED_THIS : toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { translated[index++] = toFrame(typeDescription); } return index; } @Override protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) { return instrumentedMethod.isConstructor() ? Opcodes.UNINITIALIZED_THIS.equals(frame) : toFrame(instrumentedType).equals(frame); } }, /** * A translation mode for an exit advice where the {@code this} reference is always initialized. */ EXIT { @Override protected int copy(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription methodDescription, Object[] localVariable, Object[] translated) { int index = 0; if (!instrumentedMethod.isStatic()) { translated[index++] = toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { translated[index++] = toFrame(typeDescription); } return index; } @Override protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) { return toFrame(instrumentedType).equals(frame); } }; /** * Copies the fixed parameters of the instrumented method onto the operand stack. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param methodDescription The method for which a frame is created. * @param localVariable The original local variable array. * @param translated The array containing the translated frames. * @return The amount of frames added to the translated frame array. */ protected abstract int copy(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodDescription methodDescription, Object[] localVariable, Object[] translated); /** * Checks if a variable value in a stack map frame is a legal value for describing a {@code this} reference. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param frame The frame value representing the {@code this} reference. * @return {@code true} if the value is a legal representation of the {@code this} reference. */ protected abstract boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame); } /** * A stack map frame handler for an advice method. */ protected class ForAdvice implements StackMapFrameHandler.ForAdvice { /** * The method description for which frames are translated. */ protected final MethodDescription.InDefinedShape adviceMethod; /** * A list of intermediate types to be considered as part of the instrumented method's steady signature. */ protected final TypeList requiredTypes; /** * The types that this method yields as a result. */ private final TypeList yieldedTypes; /** * The translation mode to apply for this advice method. Should be either {@link TranslationMode#ENTRY} or {@link TranslationMode#EXIT}. */ protected final TranslationMode translationMode; /** * Creates a new meta data handler for an advice method. * * @param adviceMethod The method description for which frames are translated. * @param requiredTypes A list of expected types to be considered as part of the instrumented method's steady signature. * @param yieldedTypes The types that this method yields as a result. * @param translationMode The translation mode to apply for this advice method. Should be * either {@link TranslationMode#ENTRY} or {@link TranslationMode#EXIT}. */ protected ForAdvice(MethodDescription.InDefinedShape adviceMethod, TypeList requiredTypes, TypeList yieldedTypes, TranslationMode translationMode) { this.adviceMethod = adviceMethod; this.requiredTypes = requiredTypes; this.yieldedTypes = yieldedTypes; this.translationMode = translationMode; } @Override public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, Object[] localVariable, int stackSize, Object[] stack) { Default.this.translateFrame(methodVisitor, translationMode, adviceMethod, requiredTypes, type, localVariableLength, localVariable, stackSize, stack); } @Override public void injectReturnFrame(MethodVisitor methodVisitor) { if (!expandFrames && currentFrameDivergence == 0) { if (yieldedTypes.isEmpty() || adviceMethod.getReturnType().represents(void.class)) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{toFrame(adviceMethod.getReturnType().asErasure())}); } } else { injectFullFrame(methodVisitor, requiredTypes, yieldedTypes.isEmpty() || adviceMethod.getReturnType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(adviceMethod.getReturnType().asErasure())); } } @Override public void injectExceptionFrame(MethodVisitor methodVisitor) { if (!expandFrames && currentFrameDivergence == 0) { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Type.getInternalName(Throwable.class)}); } else { injectFullFrame(methodVisitor, requiredTypes, Collections.singletonList(TypeDescription.THROWABLE)); } } @Override public void injectCompletionFrame(MethodVisitor methodVisitor, boolean secondary) { if ((!expandFrames && currentFrameDivergence == 0 && yieldedTypes.size() < 4)) { if (secondary || yieldedTypes.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { Object[] local = new Object[yieldedTypes.size()]; int index = 0; for (TypeDescription typeDescription : yieldedTypes) { local[index++] = toFrame(typeDescription); } methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY); } } else { injectFullFrame(methodVisitor, CompoundList.of(requiredTypes, yieldedTypes), Collections.emptyList()); } } } } } /** * A dispatcher for implementing advice. */ protected interface Dispatcher { /** * Indicates that a method does not represent advice and does not need to be visited. */ MethodVisitor IGNORE_METHOD = null; /** * Expresses that an annotation should not be visited. */ AnnotationVisitor IGNORE_ANNOTATION = null; /** * Returns {@code true} if this dispatcher is alive. * * @return {@code true} if this dispatcher is alive. */ boolean isAlive(); /** * A dispatcher that is not yet resolved. */ interface Unresolved extends Dispatcher { /** * Indicates that this dispatcher requires access to the class file declaring the advice method. * * @return {@code true} if this dispatcher requires access to the advice method's class file. */ boolean isBinary(); /** * Resolves this dispatcher as a dispatcher for entering a method. * * @param userFactories A list of custom factories for binding parameters of an advice method. * @param classReader A class reader to query for a class file which might be {@code null} if this dispatcher is not binary. * @return This dispatcher as a dispatcher for entering a method. */ Resolved.ForMethodEnter asMethodEnter(List> userFactories, ClassReader classReader); /** * Resolves this dispatcher as a dispatcher for exiting a method. * * @param userFactories A list of custom factories for binding parameters of an advice method. * @param classReader A class reader to query for a class file which might be {@code null} if this dispatcher is not binary. * @param dispatcher The dispatcher for entering a method. * @return This dispatcher as a dispatcher for exiting a method. */ Resolved.ForMethodExit asMethodExitTo(List> userFactories, ClassReader classReader, Resolved.ForMethodEnter dispatcher); } /** * A suppression handler for optionally suppressing exceptions. */ interface SuppressionHandler { /** * Binds the suppression handler for instrumenting a specific method. * * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @return A bound version of the suppression handler. */ Bound bind(StackManipulation exceptionHandler); /** * A producer for a default return value if this is applicable. */ interface ReturnValueProducer { /** * Instructs this return value producer to assure the production of a default value for the return type of the currently handled method. * * @param methodVisitor The method visitor to write the default value to. */ void onDefaultValue(MethodVisitor methodVisitor); } /** * A bound version of a suppression handler that must not be reused. */ interface Bound { /** * Invoked to prepare the suppression handler, i.e. to write an exception handler entry if appropriate. * * @param methodVisitor The method visitor to apply the preparation to. */ void onPrepare(MethodVisitor methodVisitor); /** * Invoked at the start of a method. * * @param methodVisitor The method visitor of the instrumented method. */ void onStart(MethodVisitor methodVisitor); /** * Invoked at the end of a method. * * @param methodVisitor The method visitor of the instrumented method. * @param implementationContext The implementation context to use. * @param methodSizeHandler The advice method's method size handler. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param returnValueProducer A producer for defining a default return value of the advised method. */ void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, ReturnValueProducer returnValueProducer); /** * Invoked at the end of a method. Additionally indicates that the handler block should be surrounding by a skipping instruction. This method * is always followed by a stack map frame (if it is required for the class level and class writer setting). * * @param methodVisitor The method visitor of the instrumented method. * @param implementationContext The implementation context to use. * @param methodSizeHandler The advice method's method size handler. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param returnValueProducer A producer for defining a default return value of the advised method. */ void onEndSkipped(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, ReturnValueProducer returnValueProducer); } /** * A non-operational suppression handler that does not suppress any method. */ enum NoOp implements SuppressionHandler, Bound { /** * The singleton instance. */ INSTANCE; @Override public Bound bind(StackManipulation exceptionHandler) { return this; } @Override public void onPrepare(MethodVisitor methodVisitor) { /* do nothing */ } @Override public void onStart(MethodVisitor methodVisitor) { /* do nothing */ } @Override public void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, ReturnValueProducer returnValueProducer) { /* do nothing */ } @Override public void onEndSkipped(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, ReturnValueProducer returnValueProducer) { /* do nothing */ } } /** * A suppression handler that suppresses a given throwable type. */ @EqualsAndHashCode class Suppressing implements SuppressionHandler { /** * The suppressed throwable type. */ private final TypeDescription suppressedType; /** * Creates a new suppressing suppression handler. * * @param suppressedType The suppressed throwable type. */ protected Suppressing(TypeDescription suppressedType) { this.suppressedType = suppressedType; } /** * Resolves an appropriate suppression handler. * * @param suppressedType The suppressed type or {@link NoExceptionHandler} if no type should be suppressed. * @return An appropriate suppression handler. */ protected static SuppressionHandler of(TypeDescription suppressedType) { return suppressedType.represents(NoExceptionHandler.class) ? NoOp.INSTANCE : new Suppressing(suppressedType); } @Override public SuppressionHandler.Bound bind(StackManipulation exceptionHandler) { return new Bound(suppressedType, exceptionHandler); } /** * An active, bound suppression handler. */ protected static class Bound implements SuppressionHandler.Bound { /** * The suppressed throwable type. */ private final TypeDescription suppressedType; /** * The stack manipulation to apply within a suppression handler. */ private final StackManipulation exceptionHandler; /** * A label indicating the start of the method. */ private final Label startOfMethod; /** * A label indicating the end of the method. */ private final Label endOfMethod; /** * Creates a new active, bound suppression handler. * * @param suppressedType The suppressed throwable type. * @param exceptionHandler The stack manipulation to apply within a suppression handler. */ protected Bound(TypeDescription suppressedType, StackManipulation exceptionHandler) { this.suppressedType = suppressedType; this.exceptionHandler = exceptionHandler; startOfMethod = new Label(); endOfMethod = new Label(); } @Override public void onPrepare(MethodVisitor methodVisitor) { methodVisitor.visitTryCatchBlock(startOfMethod, endOfMethod, endOfMethod, suppressedType.getInternalName()); } @Override public void onStart(MethodVisitor methodVisitor) { methodVisitor.visitLabel(startOfMethod); } @Override public void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, ReturnValueProducer returnValueProducer) { methodVisitor.visitLabel(endOfMethod); stackMapFrameHandler.injectExceptionFrame(methodVisitor); methodSizeHandler.requireStackSize(1 + exceptionHandler.apply(methodVisitor, implementationContext).getMaximalSize()); returnValueProducer.onDefaultValue(methodVisitor); } @Override public void onEndSkipped(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, ReturnValueProducer returnValueProducer) { Label endOfHandler = new Label(); methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfHandler); onEnd(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, returnValueProducer); methodVisitor.visitLabel(endOfHandler); } } } } /** * Represents a resolved dispatcher. */ interface Resolved extends Dispatcher { /** * Binds this dispatcher for resolution to a specific method. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param methodVisitor The method visitor for writing the instrumented method. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param methodSizeHandler A handler for computing the method size requirements. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param exceptionHandler The stack manipulation to apply within a suppression handler. * @return A dispatcher that is bound to the instrumented method. */ Bound bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler); /** * Represents a resolved dispatcher for entering a method. */ interface ForMethodEnter extends Resolved { /** * Returns the type that this dispatcher supplies as a result of its advice or a description of {@code void} if * no type is supplied as a result of the enter advice. * * @return The type that this dispatcher supplies as a result of its advice or a description of {@code void}. */ TypeDefinition getEnterType(); /** * Returns {@code true} if the first discovered line number information should be prepended to the advice code. * * @return {@code true} if the first discovered line number information should be prepended to the advice code. */ boolean isPrependLineNumber(); @Override Bound.ForMethodEnter bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler); /** * A skip dispatcher is responsible for skipping the instrumented method depending on the * return value of the enter advice method. */ interface SkipDispatcher { /** * Applies this skip dispatcher. * * @param methodVisitor The method visitor to write to. * @param methodSizeHandler The method size handler of the advice method to use. * @param stackMapFrameHandler The stack map frame handler of the advice method to use. * @param instrumentedMethod The instrumented method. * @param skipHandler The skip handler to use. */ void apply(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, MethodDescription instrumentedMethod, Bound.SkipHandler skipHandler); /** * A disabled skip dispatcher where the instrumented method is always executed. */ enum Disabled implements SkipDispatcher { /** * The singleton instance. */ INSTANCE; @Override public void apply(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, MethodDescription instrumentedMethod, Bound.SkipHandler skipHandler) { /* do nothing */ } } /** * A skip dispatcher where the instrumented method is skipped for any default value of the advice method's return type. * If the return type is {@code boolean}, the relationship is inversed, where the instrumented is skipped for a {@code true} * return value. */ enum ForValue implements SkipDispatcher { /** * A skip dispatcher for a {@code boolean}, {@code byte}, {@code short}, {@code char} or {@code int} value. */ FOR_INTEGER(Opcodes.ILOAD, Opcodes.IFNE, Opcodes.IFEQ) { @Override protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) { /* do nothing */ } }, /** * A skip dispatcher for a {@code long} value. */ FOR_LONG(Opcodes.LLOAD, Opcodes.IFNE, Opcodes.IFEQ) { @Override protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) { methodVisitor.visitInsn(Opcodes.L2I); } }, /** * A skip dispatcher for a {@code float} value. */ FOR_FLOAT(Opcodes.FLOAD, Opcodes.IFNE, Opcodes.IFEQ) { @Override protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) { methodVisitor.visitInsn(Opcodes.FCONST_0); methodVisitor.visitInsn(Opcodes.FCMPL); methodSizeHandler.requireStackSize(2); } }, /** * A skip dispatcher for a {@code double} value. */ FOR_DOUBLE(Opcodes.DLOAD, Opcodes.IFNE, Opcodes.IFEQ) { @Override protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) { methodVisitor.visitInsn(Opcodes.DCONST_0); methodVisitor.visitInsn(Opcodes.DCMPL); methodSizeHandler.requireStackSize(4); } }, /** * A skip dispatcher for a reference value. */ FOR_REFERENCE(Opcodes.ALOAD, Opcodes.IFNONNULL, Opcodes.IFNULL) { @Override protected void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler) { /* do nothing */ } }; /** * The load opcode for this skip dispatcher. */ private final int load; /** * The jump instruction that triggers skipping upon observing a value's default value. */ private final int defaultJump; /** * The jump instruction that triggers skipping upon observing a value's non-default value. */ private final int nonDefaultJump; /** * Creates a new skip dispatcher. * * @param load The load opcode for this skip dispatcher. * @param defaultJump The jump instruction that triggers skipping upon observing a value's default value. * @param nonDefaultJump The jump instruction that triggers skipping upon observing a value's non-default value. */ ForValue(int load, int defaultJump, int nonDefaultJump) { this.load = load; this.defaultJump = defaultJump; this.nonDefaultJump = nonDefaultJump; } /** * Creates an appropriate skip dispatcher. * * @param typeDefinition The type for which to skip a value. * @param inverted {@code true} if the skip condition should be inverted to trigger upon non-default values. * @return An appropriate skip dispatcher. */ protected static SkipDispatcher of(TypeDefinition typeDefinition, boolean inverted) { ForValue skipDispatcher; if (typeDefinition.represents(long.class)) { skipDispatcher = FOR_LONG; } else if (typeDefinition.represents(float.class)) { skipDispatcher = FOR_FLOAT; } else if (typeDefinition.represents(double.class)) { skipDispatcher = FOR_DOUBLE; } else if (typeDefinition.represents(void.class)) { throw new IllegalStateException("Cannot skip on default value for void return type"); } else if (typeDefinition.isPrimitive()) { // anyOf(byte, short, char, int) skipDispatcher = FOR_INTEGER; } else { skipDispatcher = FOR_REFERENCE; } return inverted ? skipDispatcher.inverted() : skipDispatcher; } @Override public void apply(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, MethodDescription instrumentedMethod, Bound.SkipHandler skipHandler) { doApply(methodVisitor, methodSizeHandler, stackMapFrameHandler, instrumentedMethod, skipHandler, false); } /** * Applies this skip dispatcher. * * @param methodVisitor The method visitor to write to. * @param methodSizeHandler The method size handler of the advice method to use. * @param stackMapFrameHandler The stack map frame handler of the advice method to use. * @param instrumentedMethod The instrumented method. * @param skipHandler The skip handler to use. * @param inverted {@code true} if the skip condition should be inverted. */ protected void doApply(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, MethodDescription instrumentedMethod, Bound.SkipHandler skipHandler, boolean inverted) { methodVisitor.visitVarInsn(load, instrumentedMethod.getStackSize()); convertValue(methodVisitor, methodSizeHandler); Label noSkip = new Label(); methodVisitor.visitJumpInsn(inverted ? nonDefaultJump : defaultJump, noSkip); skipHandler.apply(methodVisitor); methodVisitor.visitLabel(noSkip); stackMapFrameHandler.injectCompletionFrame(methodVisitor, true); } /** * Converts the return value to an {@code int} value. * * @param methodVisitor The method visitor to use. * @param methodSizeHandler The method size handler of the advice method to use. */ protected abstract void convertValue(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler); /** * Returns an inverted version of this skip dispatcher. * * @return An inverted version of this skip dispatcher. */ private SkipDispatcher inverted() { return new Inverted(); } /** * An inverted version of a value-based skipped dispatcher that triggers upon observing a non-default value. */ protected class Inverted implements SkipDispatcher { @Override public void apply(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, MethodDescription instrumentedMethod, Bound.SkipHandler skipHandler) { doApply(methodVisitor, methodSizeHandler, stackMapFrameHandler, instrumentedMethod, skipHandler, true); } /** * Returns the outer instance. * * @return The outer instance. */ private SkipDispatcher getOuter() { return ForValue.this; } @Override // HE: Remove when Lombok support for getOuter is added. public int hashCode() { return ForValue.this.hashCode(); } @Override // HE: Remove when Lombok support for getOuter is added. public boolean equals(Object other) { if (other == this) return true; if (other == null || other.getClass() != getClass()) return false; Inverted inverted = (Inverted) other; return inverted.getOuter().equals(ForValue.this); } } } /** * A skip dispatcher that skips a value if it is of a given instance. */ @EqualsAndHashCode class ForType implements SkipDispatcher { /** * The type for which to skip instances. */ private final TypeDescription typeDescription; /** * Creates a new skip dispatcher for a given type. * * @param typeDescription The type for which to skip instances. */ protected ForType(TypeDescription typeDescription) { this.typeDescription = typeDescription; } /** * Creates a skip dispatcher for an advice method. * * @param adviceMethod The advice method for which to resolve a skip dispatcher. * @return An appropriate skip dispatcher. */ public static SkipDispatcher of(MethodDescription adviceMethod) { return of(adviceMethod.getDeclaredAnnotations() .ofType(OnMethodEnter.class) .getValue(SKIP_ON) .resolve(TypeDescription.class), adviceMethod); } /** * Creates a skip dispatcher for a given annotation type and advice method. * * @param typeDescription The type that was specified as an annotation value. * @param adviceMethod The advice method. * @return An appropriate skip dispatcher. */ protected static SkipDispatcher of(TypeDescription typeDescription, MethodDescription adviceMethod) { if (typeDescription.represents(void.class)) { return Disabled.INSTANCE; } else if (typeDescription.represents(OnDefaultValue.class)) { return ForValue.of(adviceMethod.getReturnType(), false); } else if (typeDescription.represents(OnNonDefaultValue.class)) { return ForValue.of(adviceMethod.getReturnType(), true); } else if (typeDescription.isPrimitive() || adviceMethod.getReturnType().isPrimitive()) { throw new IllegalStateException("Cannot skip method by instance type for primitive return value on " + adviceMethod); } else { return new ForType(typeDescription); } } @Override public void apply(MethodVisitor methodVisitor, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, MethodDescription instrumentedMethod, Bound.SkipHandler skipHandler) { methodVisitor.visitVarInsn(Opcodes.ALOAD, instrumentedMethod.getStackSize()); methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, typeDescription.getInternalName()); Label noSkip = new Label(); methodVisitor.visitJumpInsn(Opcodes.IFEQ, noSkip); skipHandler.apply(methodVisitor); methodVisitor.visitLabel(noSkip); stackMapFrameHandler.injectCompletionFrame(methodVisitor, true); } } } } /** * Represents a resolved dispatcher for exiting a method. */ interface ForMethodExit extends Resolved { /** * Returns the type of throwable for which this exit advice is supposed to be invoked. * * @return The {@link Throwable} type for which to invoke this exit advice or a description of {@link NoExceptionHandler} * if this exit advice does not expect to be invoked upon any throwable. */ TypeDescription getThrowable(); @Override Bound.ForMethodExit bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler); } } /** * A bound resolution of an advice method. */ interface Bound { /** * Prepares the advice method's exception handlers. */ void prepare(); /** * A skip handler is responsible for writing code that skips the invocation of the original code * within the instrumented method. */ interface SkipHandler { /** * Applies this skip handler. * * @param methodVisitor The method visitor to write the code to. */ void apply(MethodVisitor methodVisitor); } /** * A bound dispatcher for a method enter. */ interface ForMethodEnter extends Bound { /** * Applies this dispatcher. * * @param skipHandler The skip handler to use. */ void apply(SkipHandler skipHandler); } /** * A bound dispatcher for a method exit. */ interface ForMethodExit extends Bound { /** * Applies this dispatcher. */ void apply(); } } /** * An implementation for inactive devise that does not write any byte code. */ enum Inactive implements Dispatcher.Unresolved, Resolved.ForMethodEnter, Resolved.ForMethodExit, Bound.ForMethodEnter, Bound.ForMethodExit { /** * The singleton instance. */ INSTANCE; @Override public boolean isAlive() { return false; } @Override public boolean isBinary() { return false; } @Override public TypeDescription getThrowable() { return NoExceptionHandler.DESCRIPTION; } @Override public TypeDefinition getEnterType() { return TypeDescription.VOID; } @Override public boolean isPrependLineNumber() { return false; } @Override public Resolved.ForMethodEnter asMethodEnter(List> userFactories, ClassReader classReader) { return this; } @Override public Resolved.ForMethodExit asMethodExitTo(List> userFactories, ClassReader classReader, Resolved.ForMethodEnter dispatcher) { return this; } @Override public void prepare() { /* do nothing */ } @Override public void apply() { /* do nothing */ } @Override public void apply(SkipHandler skipHandler) { /* do nothing */ } @Override public Inactive bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler) { return this; } } /** * A dispatcher for an advice method that is being inlined into the instrumented method. */ @EqualsAndHashCode class Inlining implements Unresolved { /** * The advice method. */ protected final MethodDescription.InDefinedShape adviceMethod; /** * Creates a dispatcher for inlined advice method. * * @param adviceMethod The advice method. */ protected Inlining(MethodDescription.InDefinedShape adviceMethod) { this.adviceMethod = adviceMethod; } @Override public boolean isAlive() { return true; } @Override public boolean isBinary() { return true; } @Override public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List> userFactories, ClassReader classReader) { return new Resolved.ForMethodEnter(adviceMethod, userFactories, classReader); } @Override public Dispatcher.Resolved.ForMethodExit asMethodExitTo(List> userFactories, ClassReader classReader, Dispatcher.Resolved.ForMethodEnter dispatcher) { return Resolved.ForMethodExit.of(adviceMethod, userFactories, classReader, dispatcher.getEnterType()); } /** * A resolved version of a dispatcher. */ protected abstract static class Resolved implements Dispatcher.Resolved { /** * Indicates a read-only mapping for an offset. */ private static final boolean READ_ONLY = true; /** * The represented advice method. */ protected final MethodDescription.InDefinedShape adviceMethod; /** * A class reader to query for the class file of the advice method. */ protected final ClassReader classReader; /** * An unresolved mapping of offsets of the advice method based on the annotations discovered on each method parameter. */ protected final Map offsetMappings; /** * The suppression handler to use. */ protected final SuppressionHandler suppressionHandler; /** * Creates a new resolved version of a dispatcher. * * @param adviceMethod The represented advice method. * @param factories A list of factories to resolve for the parameters of the advice method. * @param classReader A class reader to query for the class file of the advice method. * @param throwableType The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions. */ protected Resolved(MethodDescription.InDefinedShape adviceMethod, List> factories, ClassReader classReader, TypeDescription throwableType) { this.adviceMethod = adviceMethod; Map> offsetMappings = new HashMap>(); for (OffsetMapping.Factory factory : factories) { offsetMappings.put(new TypeDescription.ForLoadedType(factory.getAnnotationType()), factory); } this.offsetMappings = new HashMap(); for (ParameterDescription.InDefinedShape parameterDescription : adviceMethod.getParameters()) { OffsetMapping offsetMapping = null; for (AnnotationDescription annotationDescription : parameterDescription.getDeclaredAnnotations()) { OffsetMapping.Factory factory = offsetMappings.get(annotationDescription.getAnnotationType()); if (factory != null) { @SuppressWarnings("unchecked") OffsetMapping current = factory.make(parameterDescription, (AnnotationDescription.Loadable) annotationDescription.prepare(factory.getAnnotationType()), OffsetMapping.Factory.AdviceType.INLINING); if (offsetMapping == null) { offsetMapping = current; } else { throw new IllegalStateException(parameterDescription + " is bound to both " + current + " and " + offsetMapping); } } } this.offsetMappings.put(parameterDescription.getOffset(), offsetMapping == null ? new OffsetMapping.ForArgument.Unresolved(parameterDescription) : offsetMapping); } this.classReader = classReader; suppressionHandler = SuppressionHandler.Suppressing.of(throwableType); } @Override public boolean isAlive() { return true; } /** * Applies a resolution for a given instrumented method. * * @param methodVisitor A method visitor for writing byte code to the instrumented method. * @param implementationContext The implementation context to use. * @param assigner The assigner to use. * @param methodSizeHandler A handler for computing the method size requirements. * @param stackMapFrameHandler A handler for translating and injecting stack map frames. * @param instrumentedType A description of the instrumented type. * @param instrumentedMethod A description of the instrumented method. * @param suppressionHandler The bound suppression handler that is used for suppressing exceptions of this advice method. * @return A method visitor for visiting the advice method's byte code. */ protected abstract MethodVisitor apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, SuppressionHandler.Bound suppressionHandler); @Override // HE: Remove after Lombok resolves ambiguous type names correctly. public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; Inlining.Resolved resolved = (Inlining.Resolved) object; return adviceMethod.equals(resolved.adviceMethod) && offsetMappings.equals(resolved.offsetMappings) && suppressionHandler.equals(resolved.suppressionHandler); } @Override // HE: Remove after Lombok resolves ambiguous type names correctly. public int hashCode() { int result = adviceMethod.hashCode(); result = 31 * result + offsetMappings.hashCode(); result = 31 * result + suppressionHandler.hashCode(); return result; } /** * A bound advice method that copies the code by first extracting the exception table and later appending the * code of the method without copying any meta data. */ protected abstract class AdviceMethodInliner extends ClassVisitor implements Bound { /** * A description of the instrumented type. */ protected final TypeDescription instrumentedType; /** * The instrumented method. */ protected final MethodDescription instrumentedMethod; /** * The method visitor for writing the instrumented method. */ protected final MethodVisitor methodVisitor; /** * The implementation context to use. */ protected final Implementation.Context implementationContext; /** * The assigner to use. */ protected final Assigner assigner; /** * A handler for computing the method size requirements. */ protected final MethodSizeHandler.ForInstrumentedMethod methodSizeHandler; /** * A handler for translating and injecting stack map frames. */ protected final StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler; /** * A bound suppression handler that is used for suppressing exceptions of this advice method. */ protected final SuppressionHandler.Bound suppressionHandler; /** * A class reader for parsing the class file containing the represented advice method. */ protected final ClassReader classReader; /** * The labels that were found during parsing the method's exception handler in the order of their discovery. */ protected List