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

net.bytebuddy.asm.Advice Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2014 - Present Rafael Winterhalter
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.bytebuddy.asm;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.build.HashCodeAndEqualsPlugin;
import net.bytebuddy.build.RepeatedAnnotationPlugin;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.annotation.AnnotationValue;
import net.bytebuddy.description.enumeration.EnumerationDescription;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.method.ParameterList;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.description.type.TypeVariableToken;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.implementation.bytecode.Addition;
import net.bytebuddy.implementation.bytecode.ByteCodeAppender;
import net.bytebuddy.implementation.bytecode.Duplication;
import net.bytebuddy.implementation.bytecode.Removal;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.implementation.bytecode.Throw;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.collection.ArrayAccess;
import net.bytebuddy.implementation.bytecode.collection.ArrayFactory;
import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
import net.bytebuddy.implementation.bytecode.constant.IntegerConstant;
import net.bytebuddy.implementation.bytecode.constant.MethodConstant;
import net.bytebuddy.implementation.bytecode.constant.NullConstant;
import net.bytebuddy.implementation.bytecode.constant.SerializedConstant;
import net.bytebuddy.implementation.bytecode.member.FieldAccess;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.ConstantValue;
import net.bytebuddy.utility.JavaConstant;
import net.bytebuddy.utility.JavaType;
import net.bytebuddy.utility.OpenedClassReader;
import net.bytebuddy.utility.nullability.AlwaysNull;
import net.bytebuddy.utility.nullability.MaybeNull;
import net.bytebuddy.utility.visitor.ExceptionTableSensitiveMethodVisitor;
import net.bytebuddy.utility.visitor.LineNumberPrependingMethodVisitor;
import net.bytebuddy.utility.visitor.StackAwareMethodVisitor;
import net.bytebuddy.jar.asm.AnnotationVisitor;
import net.bytebuddy.jar.asm.Attribute;
import net.bytebuddy.jar.asm.ClassReader;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.ClassWriter;
import net.bytebuddy.jar.asm.Label;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.jar.asm.TypePath;

import java.io.IOException;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.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 transformed 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 net.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 net.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 */ @HashCodeAndEqualsPlugin.Enhance public class Advice implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper, Implementation { /** * Indicates that no class reader is available to an advice method. */ @AlwaysNull private static final ClassReader UNDEFINED = null; /** * A reference to the {@link OnMethodEnter#skipOn()} method. */ private static final MethodDescription.InDefinedShape SKIP_ON; /** * A reference to the {@link OnMethodEnter#skipOnIndex()} method. */ private static final MethodDescription.InDefinedShape SKIP_ON_INDEX; /** * A reference to the {@link OnMethodEnter#prependLineNumber()} method. */ private static final MethodDescription.InDefinedShape PREPEND_LINE_NUMBER; /** * 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 OnMethodExit#repeatOn()} method. */ private static final MethodDescription.InDefinedShape REPEAT_ON; /** * A reference to the {@link OnMethodExit#repeatOnIndex()} method. */ private static final MethodDescription.InDefinedShape REPEAT_ON_INDEX; /** * A reference to the {@link OnMethodExit#onThrowable()} method. */ private static final MethodDescription.InDefinedShape ON_THROWABLE; /** * A reference to the {@link OnMethodExit#backupArguments()} method. */ private static final MethodDescription.InDefinedShape BACKUP_ARGUMENTS; /** * 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; /* * Extracts the annotation values for the enter and exit advice annotations. */ static { MethodList enter = TypeDescription.ForLoadedType.of(OnMethodEnter.class).getDeclaredMethods(); SKIP_ON = enter.filter(named("skipOn")).getOnly(); SKIP_ON_INDEX = enter.filter(named("skipOnIndex")).getOnly(); PREPEND_LINE_NUMBER = enter.filter(named("prependLineNumber")).getOnly(); INLINE_ENTER = enter.filter(named("inline")).getOnly(); SUPPRESS_ENTER = enter.filter(named("suppress")).getOnly(); MethodList exit = TypeDescription.ForLoadedType.of(OnMethodExit.class).getDeclaredMethods(); REPEAT_ON = exit.filter(named("repeatOn")).getOnly(); REPEAT_ON_INDEX = exit.filter(named("repeatOnIndex")).getOnly(); ON_THROWABLE = exit.filter(named("onThrowable")).getOnly(); BACKUP_ARGUMENTS = exit.filter(named("backupArguments")).getOnly(); INLINE_EXIT = exit.filter(named("inline")).getOnly(); SUPPRESS_EXIT = exit.filter(named("suppress")).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 exception handler to apply. */ private final ExceptionHandler 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, ExceptionHandler.Default.SUPPRESSING, 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 exception handler to apply. * @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, ExceptionHandler 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(TypeDescription.ForLoadedType.of(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, PostProcessor.NoOp.INSTANCE, classFileLocator, Collections.>emptyList(), Delegator.ForRegularInvocation.Factory.INSTANCE); } /** * Creates a new advice. * * @param advice A description of the type declaring the advice. * @param postProcessorFactory The post processor factory to use. * @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. * @param delegatorFactory The delegator factory to use. * @return A method visitor wrapper representing the supplied advice. */ protected static Advice to(TypeDescription advice, PostProcessor.Factory postProcessorFactory, ClassFileLocator classFileLocator, List> userFactories, Delegator.Factory delegatorFactory) { Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE; for (MethodDescription.InDefinedShape methodDescription : advice.getDeclaredMethods()) { methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription, delegatorFactory); methodExit = locate(OnMethodExit.class, INLINE_EXIT, methodExit, methodDescription, delegatorFactory); } if (!methodEnter.isAlive() && !methodExit.isAlive()) { throw new IllegalArgumentException("No advice defined by " + advice); } try { ClassReader classReader = methodEnter.isBinary() || methodExit.isBinary() ? OpenedClassReader.of(classFileLocator.locate(advice.getName()).resolve()) : UNDEFINED; return new Advice(methodEnter.asMethodEnter(userFactories, classReader, methodExit, postProcessorFactory), methodExit.asMethodExit(userFactories, classReader, methodEnter, postProcessorFactory)); } 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(TypeDescription.ForLoadedType.of(enterAdvice), TypeDescription.ForLoadedType.of(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, PostProcessor.NoOp.INSTANCE, classFileLocator, Collections.>emptyList(), Delegator.ForRegularInvocation.Factory.INSTANCE); } /** * Creates a new advice. * * @param enterAdvice The type declaring the enter advice. * @param exitAdvice The type declaring the exit advice. * @param postProcessorFactory The post processor factory to use. * @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. * @param delegatorFactory The delegator factory to use. * @return A method visitor wrapper representing the supplied advice. */ protected static Advice to(TypeDescription enterAdvice, TypeDescription exitAdvice, PostProcessor.Factory postProcessorFactory, ClassFileLocator classFileLocator, List> userFactories, Delegator.Factory delegatorFactory) { Dispatcher.Unresolved methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE; for (MethodDescription.InDefinedShape methodDescription : enterAdvice.getDeclaredMethods()) { methodEnter = locate(OnMethodEnter.class, INLINE_ENTER, methodEnter, methodDescription, delegatorFactory); } 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, delegatorFactory); } if (!methodExit.isAlive()) { throw new IllegalArgumentException("No exit advice defined by " + exitAdvice); } try { return new Advice(methodEnter.asMethodEnter(userFactories, methodEnter.isBinary() ? OpenedClassReader.of(classFileLocator.locate(enterAdvice.getName()).resolve()) : UNDEFINED, methodExit, postProcessorFactory), methodExit.asMethodExit(userFactories, methodExit.isBinary() ? OpenedClassReader.of(classFileLocator.locate(exitAdvice.getName()).resolve()) : UNDEFINED, methodEnter, postProcessorFactory)); } 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 the previous dispatcher if found. * @param methodDescription The method description that is considered as an advice method. * @param delegatorFactory The delegator factory to use. * @return A resolved dispatcher or the previous dispatcher if none was found. */ private static Dispatcher.Unresolved locate(Class type, MethodDescription.InDefinedShape property, Dispatcher.Unresolved dispatcher, MethodDescription.InDefinedShape methodDescription, Delegator.Factory delegatorFactory) { 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, delegatorFactory); } } /** * 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().invokable(matcher, this); } /** * {@inheritDoc} */ 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) { if (methodEnter.isPrependLineNumber()) { methodVisitor = new LineNumberPrependingMethodVisitor(methodVisitor); } if (!methodExit.isAlive()) { return new AdviceVisitor.WithoutExitAdvice(methodVisitor, implementationContext, assigner, exceptionHandler.resolve(instrumentedMethod, instrumentedType), instrumentedType, instrumentedMethod, methodEnter, writerFlags, readerFlags); } else if (methodExit.getThrowable().represents(NoExceptionHandler.class)) { return new AdviceVisitor.WithExitAdvice.WithoutExceptionHandling(methodVisitor, implementationContext, assigner, exceptionHandler.resolve(instrumentedMethod, instrumentedType), 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.resolve(instrumentedMethod, instrumentedType), instrumentedType, instrumentedMethod, methodEnter, methodExit, writerFlags, readerFlags, methodExit.getThrowable()); } } /** * {@inheritDoc} */ public InstrumentedType prepare(InstrumentedType instrumentedType) { return delegate.prepare(instrumentedType); } /** * {@inheritDoc} */ 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() { return withExceptionHandler(ExceptionHandler.Default.PRINTING); } /** * 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 withExceptionHandler(new ExceptionHandler.Simple(exceptionHandler)); } /** * Configures this advice to execute the given exception handler 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(ExceptionHandler 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 argumentHandler The argument handler to use for resolving offsets of the local variable array of the instrumented method. * @param sort The sort of the advice method being resolved. * @return A suitable target mapping. */ Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort); /** * 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); /** * An adapter class for a target that only can be read. */ abstract class AbstractReadOnlyAdapter implements Target { /** * {@inheritDoc} */ public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only value"); } /** * {@inheritDoc} */ public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to read-only 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. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public StackManipulation resolveRead() { return new StackManipulation.Compound(DefaultValue.of(typeDefinition), readAssignment); } /** * A read-only target for a default value. */ public static class ReadOnly extends ForDefaultValue { /** * Creates a new writable target for a default value. * * @param typeDefinition The represented type. */ public 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. */ public ReadOnly(TypeDefinition typeDefinition, StackManipulation readAssignment) { super(typeDefinition, readAssignment); } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only default value"); } /** * {@inheritDoc} */ public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to read-only default value"); } } /** * A read-write target for a default value. */ public static class ReadWrite extends ForDefaultValue { /** * Creates a new read-only target for a default value. * * @param typeDefinition The represented type. */ public 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. */ public ReadWrite(TypeDefinition typeDefinition, StackManipulation readAssignment) { super(typeDefinition, readAssignment); } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { return Removal.of(typeDefinition); } /** * {@inheritDoc} */ public StackManipulation resolveIncrement(int value) { return StackManipulation.Trivial.INSTANCE; } } } /** * A target for an offset mapping that represents a local variable. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public StackManipulation resolveRead() { return new StackManipulation.Compound(MethodVariableAccess.of(typeDefinition).loadFrom(offset), readAssignment); } /** * A target for a read-only mapping of a local variable. */ public static class ReadOnly extends ForVariable { /** * Creates a read-only mapping for a local variable. * * @param typeDefinition The represented type. * @param offset The value's offset. */ public ReadOnly(TypeDefinition typeDefinition, int offset) { this(typeDefinition, offset, StackManipulation.Trivial.INSTANCE); } /** * 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. */ public ReadOnly(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment) { super(typeDefinition, offset, readAssignment); } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only parameter " + typeDefinition + " at " + offset); } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance public 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 typeDefinition The represented type. * @param offset The value's offset. */ public ReadWrite(TypeDefinition typeDefinition, int offset) { this(typeDefinition, offset, StackManipulation.Trivial.INSTANCE, StackManipulation.Trivial.INSTANCE); } /** * 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. */ public ReadWrite(TypeDefinition typeDefinition, int offset, StackManipulation readAssignment, StackManipulation writeAssignment) { super(typeDefinition, offset, readAssignment); this.writeAssignment = writeAssignment; } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { return new StackManipulation.Compound(writeAssignment, MethodVariableAccess.of(typeDefinition).storeAt(offset)); } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public StackManipulation resolveRead() { return ArrayFactory.forType(target).withValues(valueReads); } /** * {@inheritDoc} */ 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. */ public 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. */ public ReadOnly(TypeDescription.Generic target, List valueReads) { super(target, valueReads); } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance public 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. */ public ReadWrite(TypeDescription.Generic target, List valueReads, List valueWrites) { super(target, valueReads); this.valueWrites = valueWrites; } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { return new StackManipulation.Compound(ArrayAccess.of(target).forEach(valueWrites), Removal.SINGLE); } } } /** * A target for an offset mapping that loads a field value. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ 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. */ public static class ReadOnly extends ForField { /** * Creates a new read-only mapping for a field. * * @param fieldDescription The field value to load. */ public ReadOnly(FieldDescription fieldDescription) { this(fieldDescription, StackManipulation.Trivial.INSTANCE); } /** * 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. */ public ReadOnly(FieldDescription fieldDescription, StackManipulation readAssignment) { super(fieldDescription, readAssignment); } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to read-only field value"); } /** * {@inheritDoc} */ public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to read-only field value"); } } /** * A write-only mapping for a field value, typically to be used for constructors prior to invoking the super-constructor. */ @HashCodeAndEqualsPlugin.Enhance public static class WriteOnly implements Target { /** * The field value to load. */ private final FieldDescription fieldDescription; /** * An assignment to apply prior to a field write. */ private final StackManipulation writeAssignment; /** * Creates a write-only mapping for a field value. * * @param fieldDescription The field value to load. * @param writeAssignment An assignment to apply prior to a field write. */ protected WriteOnly(FieldDescription fieldDescription, StackManipulation writeAssignment) { this.fieldDescription = fieldDescription; this.writeAssignment = writeAssignment; } /** * {@inheritDoc} */ public StackManipulation resolveRead() { throw new IllegalStateException("Cannot read write-only field value"); } /** * {@inheritDoc} */ 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(writeAssignment, preparation, FieldAccess.forField(fieldDescription).write()); } /** * {@inheritDoc} */ public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot increment write-only field value"); } } /** * A mapping for a writable field. */ @HashCodeAndEqualsPlugin.Enhance public 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. */ public ReadWrite(FieldDescription fieldDescription) { this(fieldDescription, StackManipulation.Trivial.INSTANCE, StackManipulation.Trivial.INSTANCE); } /** * 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. */ public ReadWrite(FieldDescription fieldDescription, StackManipulation readAssignment, StackManipulation writeAssignment) { super(fieldDescription, readAssignment); this.writeAssignment = writeAssignment; } /** * {@inheritDoc} */ 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(writeAssignment, preparation, FieldAccess.forField(fieldDescription).write()); } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance 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.of(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(@MaybeNull Object value) { return new ForStackManipulation(value == null ? NullConstant.INSTANCE : ConstantValue.Simple.wrap(value).toStackManipulation()); } /** * {@inheritDoc} */ public StackManipulation resolveRead() { return stackManipulation; } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { throw new IllegalStateException("Cannot write to constant value: " + stackManipulation); } /** * {@inheritDoc} */ public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot write to constant value: " + stackManipulation); } /** * A constant value that can be written to. */ @HashCodeAndEqualsPlugin.Enhance public static class Writable implements Target { /** * The reading stack manipulation. */ private final StackManipulation read; /** * The writing stack manipulation. */ private final StackManipulation write; /** * Creates a writable target. * * @param read The reading stack manipulation. * @param write The writing stack manipulation. */ public Writable(StackManipulation read, StackManipulation write) { this.read = read; this.write = write; } /** * {@inheritDoc} */ public StackManipulation resolveRead() { return read; } /** * {@inheritDoc} */ public StackManipulation resolveWrite() { return write; } /** * {@inheritDoc} */ public StackManipulation resolveIncrement(int value) { throw new IllegalStateException("Cannot increment mutable constant value: " + write); } } } } /** * 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 The annotation type that represents this offset mapping. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { throw new IllegalStateException("Usage of " + annotationType + " is not allowed on " + target); } } } /** * Describes the sort of the executed advice. */ enum Sort { /** * Indicates that an offset is mapped for an enter advice. */ ENTER { @Override public boolean isPremature(MethodDescription methodDescription) { return methodDescription.isConstructor(); } }, /** * Indicates that an offset is mapped for an exit advice. */ EXIT { @Override public boolean isPremature(MethodDescription methodDescription) { return false; } }; /** * Checks if an advice is executed in a premature state, i.e. the instrumented method is a constructor where the super constructor is not * yet invoked. In this case, the {@code this} reference is not yet initialized and therefore not available. * * @param methodDescription The instrumented method. * @return {@code true} if the advice is executed premature for the instrumented method. */ public abstract boolean isPremature(MethodDescription methodDescription); } /** * An offset mapping for a given parameter of the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { 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.getType(), argumentHandler.argument(parameterDescription.getOffset()), 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.getType(), argumentHandler.argument(parameterDescription.getOffset()), 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. */ @HashCodeAndEqualsPlugin.Enhance 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 annotation The annotation that triggers this binding. */ protected Unresolved(TypeDescription.Generic target, AnnotationDescription.Loadable annotation) { this(target, annotation.getValue(Factory.ARGUMENT_READ_ONLY).resolve(Boolean.class), annotation.getValue(Factory.ARGUMENT_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class), annotation.getValue(Factory.ARGUMENT_VALUE).resolve(Integer.class), annotation.getValue(Factory.ARGUMENT_OPTIONAL).resolve(Boolean.class)); } /** * 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); } } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { if (optional && instrumentedMethod.getParameters().size() <= index) { return readOnly ? new Target.ForDefaultValue.ReadOnly(target) : new Target.ForDefaultValue.ReadWrite(target); } return super.resolve(instrumentedType, instrumentedMethod, assigner, argumentHandler, sort); } /** * A factory for a mapping of a parameter of the instrumented method. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * A description of the {@link Argument#value()} method. */ private static final MethodDescription.InDefinedShape ARGUMENT_VALUE; /** * A description of the {@link Argument#readOnly()} method. */ private static final MethodDescription.InDefinedShape ARGUMENT_READ_ONLY; /** * A description of the {@link Argument#typing()} method. */ private static final MethodDescription.InDefinedShape ARGUMENT_TYPING; /** * A description of the {@link Argument#optional()} method. */ private static final MethodDescription.InDefinedShape ARGUMENT_OPTIONAL; /* * Resolves annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(Argument.class).getDeclaredMethods(); ARGUMENT_VALUE = methods.filter(named("value")).getOnly(); ARGUMENT_READ_ONLY = methods.filter(named("readOnly")).getOnly(); ARGUMENT_TYPING = methods.filter(named("typing")).getOnly(); ARGUMENT_OPTIONAL = methods.filter(named("optional")).getOnly(); } /** * {@inheritDoc} */ public Class getAnnotationType() { return Argument.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(ARGUMENT_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot define writable field access for " + target + " when using delegation"); } else { return new ForArgument.Unresolved(target.getType(), annotation); } } } } /** * An offset mapping for a specific parameter of the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance 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. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance class ForThisReference implements OffsetMapping { /** * 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, AnnotationDescription.Loadable annotation) { this(target, annotation.getValue(Factory.THIS_READ_ONLY).resolve(Boolean.class), annotation.getValue(Factory.THIS_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class), annotation.getValue(Factory.THIS_OPTIONAL).resolve(Boolean.class)); } /** * 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { if (instrumentedMethod.isStatic() || sort.isPremature(instrumentedMethod)) { 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(), argumentHandler.argument(ArgumentHandler.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(), argumentHandler.argument(ArgumentHandler.THIS_REFERENCE), readAssignment, writeAssignment); } } /** * A factory for creating a {@link ForThisReference} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * A description of the {@link This#readOnly()} method. */ private static final MethodDescription.InDefinedShape THIS_READ_ONLY; /** * A description of the {@link This#typing()} method. */ private static final MethodDescription.InDefinedShape THIS_TYPING; /** * A description of the {@link This#optional()} method. */ private static final MethodDescription.InDefinedShape THIS_OPTIONAL; /* * Resolves annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(This.class).getDeclaredMethods(); THIS_READ_ONLY = methods.filter(named("readOnly")).getOnly(); THIS_TYPING = methods.filter(named("typing")).getOnly(); THIS_OPTIONAL = methods.filter(named("optional")).getOnly(); } /** * {@inheritDoc} */ public Class getAnnotationType() { return This.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(THIS_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot write to this reference for " + target + " in read-only context"); } else { return new ForThisReference(target.getType(), annotation); } } } } /** * An offset mapping that maps an array containing all arguments of the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance 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; /** * {@code true} if the method's target instance should be considered an element of the produced array. */ private final boolean includeSelf; /** * {@code true} if a {@code null} value should be assigned if the * instrumented method does not declare any parameters. */ private final boolean nullIfEmpty; /** * 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, AnnotationDescription.Loadable annotation) { this(target, annotation.getValue(Factory.ALL_ARGUMENTS_READ_ONLY).resolve(Boolean.class), annotation.getValue(Factory.ALL_ARGUMENTS_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class), annotation.getValue(Factory.ALL_ARGUMENTS_INCLUDE_SELF).resolve(Boolean.class), annotation.getValue(Factory.ALL_ARGUMENTS_NULL_IF_EMPTY).resolve(Boolean.class)); } /** * 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. * @param includeSelf {@code true} if the method's target instance should be considered * an element of the produced array. * @param nullIfEmpty {@code true} if a {@code null} value should be assigned if the * instrumented method does not declare any parameters. */ public ForAllArguments(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, boolean includeSelf, boolean nullIfEmpty) { this.target = target; this.readOnly = readOnly; this.typing = typing; this.includeSelf = includeSelf; this.nullIfEmpty = nullIfEmpty; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { if (nullIfEmpty && instrumentedMethod.getParameters().isEmpty() && (!includeSelf || instrumentedMethod.isStatic())) { return readOnly ? new Target.ForStackManipulation(NullConstant.INSTANCE) : new Target.ForStackManipulation.Writable(NullConstant.INSTANCE, Removal.SINGLE); } List reads = new ArrayList((includeSelf && !instrumentedMethod.isStatic() ? 1 : 0) + instrumentedMethod.getParameters().size()); if (includeSelf && !instrumentedMethod.isStatic()) { if (sort.isPremature(instrumentedMethod) && instrumentedMethod.isConstructor()) { throw new IllegalStateException("Cannot include self in all arguments array from " + instrumentedMethod); } StackManipulation assignment = assigner.assign(instrumentedMethod.getDeclaringType().asGenericType(), target, typing); if (!assignment.isValid()) { throw new IllegalStateException("Cannot assign " + instrumentedMethod.getDeclaringType() + " to " + target); } reads.add(new StackManipulation.Compound( MethodVariableAccess.REFERENCE.loadFrom(argumentHandler.argument(ArgumentHandler.THIS_REFERENCE)), assignment)); } for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) { StackManipulation assignment = assigner.assign(parameterDescription.getType(), target, typing); if (!assignment.isValid()) { throw new IllegalStateException("Cannot assign " + parameterDescription + " to " + target); } reads.add(new StackManipulation.Compound( MethodVariableAccess.of(parameterDescription.getType()).loadFrom(argumentHandler.argument(parameterDescription.getOffset())), assignment)); } if (readOnly) { return new Target.ForArray.ReadOnly(target, reads); } else { List writes = new ArrayList(2 * ((includeSelf && !instrumentedMethod.isStatic() ? 1 : 0) + instrumentedMethod.getParameters().size())); if (includeSelf && !instrumentedMethod.isStatic()) { StackManipulation assignment = assigner.assign(target, instrumentedMethod.getDeclaringType().asGenericType(), typing); if (!assignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + instrumentedMethod.getDeclaringType()); } writes.add(new StackManipulation.Compound( assignment, MethodVariableAccess.REFERENCE.storeAt(argumentHandler.argument(ArgumentHandler.THIS_REFERENCE)))); } for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) { StackManipulation assignment = assigner.assign(target, parameterDescription.getType(), typing); if (!assignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + parameterDescription); } writes.add(new StackManipulation.Compound( assignment, MethodVariableAccess.of(parameterDescription.getType()).storeAt(argumentHandler.argument(parameterDescription.getOffset())))); } return new Target.ForArray.ReadWrite(target, reads, writes); } } /** * 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; /** * A description of the {@link AllArguments#readOnly()} method. */ private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_READ_ONLY; /** * A description of the {@link AllArguments#typing()} method. */ private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_TYPING; /** * A description of the {@link AllArguments#includeSelf()} method. */ private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_INCLUDE_SELF; /** * A description of the {@link AllArguments#nullIfEmpty()} method. */ private static final MethodDescription.InDefinedShape ALL_ARGUMENTS_NULL_IF_EMPTY; /* * Resolves annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(AllArguments.class).getDeclaredMethods(); ALL_ARGUMENTS_READ_ONLY = methods.filter(named("readOnly")).getOnly(); ALL_ARGUMENTS_TYPING = methods.filter(named("typing")).getOnly(); ALL_ARGUMENTS_INCLUDE_SELF = methods.filter(named("includeSelf")).getOnly(); ALL_ARGUMENTS_NULL_IF_EMPTY = methods.filter(named("nullIfEmpty")).getOnly(); } /** * {@inheritDoc} */ public Class getAnnotationType() { return AllArguments.class; } /** * {@inheritDoc} */ @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.") 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.getValue(ALL_ARGUMENTS_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot define writable field access for " + target); } else { return new ForAllArguments(target.getType().represents(Object.class) ? TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class) : target.getType().getComponentType(), annotation); } } } } /** * Maps the declaring type of the instrumented method. */ enum ForInstrumentedType implements OffsetMapping { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { 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(); } @Override protected Target resolve(MethodDescription.InDefinedShape methodDescription) { return Target.ForStackManipulation.of(methodDescription); } }, /** * A constant that must be a {@link Constructor} instance. */ CONSTRUCTOR { @Override protected boolean isRepresentable(MethodDescription instrumentedMethod) { return instrumentedMethod.isConstructor(); } @Override protected Target resolve(MethodDescription.InDefinedShape methodDescription) { return Target.ForStackManipulation.of(methodDescription); } }, /** * A constant that must be a {@code java.lang.reflect.Executable} instance. */ EXECUTABLE { @Override protected boolean isRepresentable(MethodDescription instrumentedMethod) { return true; } @Override protected Target resolve(MethodDescription.InDefinedShape methodDescription) { return Target.ForStackManipulation.of(methodDescription); } }, /** * A constant that must be a {@code java.lang.invoke.MethodHandle} instance. */ METHOD_HANDLE { @Override protected boolean isRepresentable(MethodDescription instrumentedMethod) { return true; } @Override protected Target resolve(MethodDescription.InDefinedShape methodDescription) { return new Target.ForStackManipulation(JavaConstant.MethodHandle.of(methodDescription).toStackManipulation()); } }, /** * A constant that must be a {@code java.lang.invoke.MethodType} instance. */ METHOD_TYPE { @Override protected boolean isRepresentable(MethodDescription instrumentedMethod) { return true; } @Override protected Target resolve(MethodDescription.InDefinedShape methodDescription) { return new Target.ForStackManipulation(JavaConstant.MethodType.of(methodDescription).toStackManipulation()); } }; /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { if (!isRepresentable(instrumentedMethod)) { throw new IllegalStateException("Cannot represent " + instrumentedMethod + " as the specified constant"); } return resolve(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); /** * Resolves the target for a given method description. * * @param methodDescription The method description for which to resolve a constant. * @return A suitable target. */ protected abstract Target resolve(MethodDescription.InDefinedShape methodDescription); } /** * An offset mapping for a field. */ @HashCodeAndEqualsPlugin.Enhance abstract class ForField implements OffsetMapping { /** * The {@link FieldValue#value()} method. */ private static final MethodDescription.InDefinedShape FIELD_VALUE; /** * The {@link FieldValue#declaringType()}} method. */ private static final MethodDescription.InDefinedShape FIELD_DECLARING_TYPE; /** * The {@link FieldValue#readOnly()}} method. */ private static final MethodDescription.InDefinedShape FIELD_READ_ONLY; /** * The {@link FieldValue#typing()}} method. */ private static final MethodDescription.InDefinedShape FIELD_TYPING; /* * Looks up all annotation properties to avoid loading of the declaring field type. */ static { MethodList methods = TypeDescription.ForLoadedType.of(FieldValue.class).getDeclaredMethods(); FIELD_VALUE = methods.filter(named("value")).getOnly(); FIELD_DECLARING_TYPE = methods.filter(named("declaringType")).getOnly(); FIELD_READ_ONLY = methods.filter(named("readOnly")).getOnly(); FIELD_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. */ protected ForField(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing) { this.target = target; this.readOnly = readOnly; this.typing = typing; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { FieldDescription fieldDescription = resolve(instrumentedType, instrumentedMethod); if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) { throw new IllegalStateException("Cannot access non-static field " + fieldDescription + " from static method " + instrumentedMethod); } if (sort.isPremature(instrumentedMethod) && !fieldDescription.isStatic()) { if (readOnly) { throw new IllegalStateException("Cannot read " + fieldDescription + " before super constructor call"); } else { StackManipulation writeAssignment = assigner.assign(target, fieldDescription.getType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + fieldDescription); } return new Target.ForField.WriteOnly(fieldDescription.asDefined(), writeAssignment); } } else { 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. * @param instrumentedMethod The instrumented method. * @return The field being bound. */ protected abstract FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod); /** * An offset mapping for a field that is resolved from the instrumented type by its name. */ @HashCodeAndEqualsPlugin.Enhance public abstract static class Unresolved extends ForField { /** * Indicates that a name should be extracted from an accessor method. */ protected static final String BEAN_PROPERTY = ""; /** * 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. */ protected Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typing typing, String name) { super(target, readOnly, typing); this.name = name; } @Override protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { FieldLocator locator = fieldLocator(instrumentedType); FieldLocator.Resolution resolution = name.equals(BEAN_PROPERTY) ? FieldLocator.Resolution.Simple.ofBeanAccessor(locator, instrumentedMethod) : locator.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(FIELD_READ_ONLY).resolve(Boolean.class), annotation.getValue(FIELD_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class), annotation.getValue(FIELD_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. */ @HashCodeAndEqualsPlugin.Enhance 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(FIELD_READ_ONLY).resolve(Boolean.class), annotation.getValue(FIELD_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class), annotation.getValue(FIELD_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; /** * {@inheritDoc} */ public Class getAnnotationType() { return FieldValue.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(ForField.FIELD_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot write to field for " + target + " in read-only context"); } else { TypeDescription declaringType = annotation.getValue(FIELD_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. */ @HashCodeAndEqualsPlugin.Enhance 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 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.") protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) { throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType); } else if (!fieldDescription.isVisibleTo(instrumentedType)) { throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType); } return fieldDescription; } /** * A factory that binds a field. * * @param The annotation type this factory binds. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new Resolved(target.getType(), readOnly, typing, fieldDescription); } } } } /** * An offset mapping for a field handle. */ @HashCodeAndEqualsPlugin.Enhance abstract class ForFieldHandle implements OffsetMapping { /** * The access type of the represented handle. */ private final Access access; /** * Creates an offset mapping for a field handle. * * @param access The access type of the represented handle. */ protected ForFieldHandle(Access access) { this.access = access; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { FieldDescription fieldDescription = resolve(instrumentedType, instrumentedMethod); if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) { throw new IllegalStateException("Cannot access non-static field " + fieldDescription + " from static method " + instrumentedMethod); } if (sort.isPremature(instrumentedMethod) && !fieldDescription.isStatic()) { throw new IllegalStateException("Cannot access " + fieldDescription + " before super constructor call"); } else if (fieldDescription.isStatic()) { return new Target.ForStackManipulation(access.resolve(fieldDescription.asDefined()).toStackManipulation()); } else { return new Target.ForStackManipulation(new StackManipulation.Compound( access.resolve(fieldDescription.asDefined()).toStackManipulation(), MethodVariableAccess.REFERENCE.loadFrom(argumentHandler.argument(ArgumentHandler.THIS_REFERENCE)), MethodInvocation.invoke(new MethodDescription.Latent(JavaType.METHOD_HANDLE.getTypeStub(), new MethodDescription.Token("bindTo", Opcodes.ACC_PUBLIC, JavaType.METHOD_HANDLE.getTypeStub().asGenericType(), new TypeList.Generic.Explicit(TypeDefinition.Sort.describe(Object.class))))))); } } /** * Resolves the field being bound. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @return The field being bound. */ protected abstract FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod); /** * A description of the field handle's access type. */ public enum Access { /** * Determines the resolution of a getter for the method handle. */ GETTER { @Override protected JavaConstant.MethodHandle resolve(FieldDescription.InDefinedShape fieldDescription) { return JavaConstant.MethodHandle.ofGetter(fieldDescription); } }, /** * Determines the resolution of a setter for the method handle. */ SETTER { @Override protected JavaConstant.MethodHandle resolve(FieldDescription.InDefinedShape fieldDescription) { return JavaConstant.MethodHandle.ofSetter(fieldDescription); } }; /** * Returns the appropriate method handle. * * @param fieldDescription The field description to resolve the handle for. * @return The appropriate method handle. */ protected abstract JavaConstant.MethodHandle resolve(FieldDescription.InDefinedShape fieldDescription); } /** * An offset mapping for a field handle that is resolved from the instrumented type by its name. */ @HashCodeAndEqualsPlugin.Enhance public abstract static class Unresolved extends ForFieldHandle { /** * Indicates that a name should be extracted from an accessor method. */ protected static final String BEAN_PROPERTY = ""; /** * The name of the field. */ private final String name; /** * Creates an offset mapping for a field that is not yet resolved. * * @param access The access type of the represented handle. * @param name The name of the field. */ public Unresolved(Access access, String name) { super(access); this.name = name; } @Override protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { FieldLocator locator = fieldLocator(instrumentedType); FieldLocator.Resolution resolution = name.equals(BEAN_PROPERTY) ? FieldLocator.Resolution.Simple.ofBeanAccessor(locator, instrumentedMethod) : locator.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 handle with an implicit declaring type. */ public static class WithImplicitType extends Unresolved { /** * Creates an offset mapping for a field handle with an implicit declaring type. * * @param access The access type of the represented handle. * @param name The name of the field. */ public WithImplicitType(Access access, String name) { super(access, name); } @Override protected FieldLocator fieldLocator(TypeDescription instrumentedType) { return new FieldLocator.ForClassHierarchy(instrumentedType); } } /** * An offset mapping for a field handle with an explicit declaring type. */ @HashCodeAndEqualsPlugin.Enhance public static class WithExplicitType extends Unresolved { /** * The type declaring the field. */ private final TypeDescription declaringType; /** * Creates an offset mapping for a field handle with an explicit declaring type. * * @param access The access type of the represented handle. * @param name The name of the field. * @param declaringType The type declaring the field. */ public WithExplicitType(Access access, String name, TypeDescription declaringType) { super(access, 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 ForFieldHandle.Unresolved} offset mapping representing a getter. */ protected enum ReaderFactory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * The {@link FieldGetterHandle#value()} method. */ private static final MethodDescription.InDefinedShape FIELD_GETTER_HANDLE_VALUE; /** * The {@link FieldGetterHandle#declaringType()} method. */ private static final MethodDescription.InDefinedShape FIELD_GETTER_HANDLE_DECLARING_TYPE; /* * Resolves annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(FieldGetterHandle.class).getDeclaredMethods(); FIELD_GETTER_HANDLE_VALUE = methods.filter(named("value")).getOnly(); FIELD_GETTER_HANDLE_DECLARING_TYPE = methods.filter(named("declaringType")).getOnly(); } /** * {@inheritDoc} */ public Class getAnnotationType() { return FieldGetterHandle.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) { throw new IllegalStateException("Cannot assign method handle to " + target); } TypeDescription declaringType = annotation.getValue(FIELD_GETTER_HANDLE_DECLARING_TYPE).resolve(TypeDescription.class); return declaringType.represents(void.class) ? new ForFieldHandle.Unresolved.WithImplicitType(Access.GETTER, annotation.getValue(FIELD_GETTER_HANDLE_VALUE).resolve(String.class)) : new ForFieldHandle.Unresolved.WithExplicitType(Access.GETTER, annotation.getValue(FIELD_GETTER_HANDLE_VALUE).resolve(String.class), declaringType); } } /** * A factory for a {@link ForFieldHandle.Unresolved} offset mapping representing a setter. */ protected enum WriterFactory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * The {@link FieldSetterHandle#value()} method. */ private static final MethodDescription.InDefinedShape FIELD_SETTER_HANDLE_VALUE; /** * The {@link FieldSetterHandle#declaringType()} method. */ private static final MethodDescription.InDefinedShape FIELD_SETTER_HANDLE_DECLARING_TYPE; /* * Resolves annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(FieldSetterHandle.class).getDeclaredMethods(); FIELD_SETTER_HANDLE_VALUE = methods.filter(named("value")).getOnly(); FIELD_SETTER_HANDLE_DECLARING_TYPE = methods.filter(named("declaringType")).getOnly(); } /** * {@inheritDoc} */ public Class getAnnotationType() { return FieldSetterHandle.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) { throw new IllegalStateException("Cannot assign method handle to " + target); } TypeDescription declaringType = annotation.getValue(FIELD_SETTER_HANDLE_DECLARING_TYPE).resolve(TypeDescription.class); return declaringType.represents(void.class) ? new ForFieldHandle.Unresolved.WithImplicitType(Access.SETTER, annotation.getValue(FIELD_SETTER_HANDLE_VALUE).resolve(String.class)) : new ForFieldHandle.Unresolved.WithExplicitType(Access.SETTER, annotation.getValue(FIELD_SETTER_HANDLE_VALUE).resolve(String.class), declaringType); } } } /** * A binding for an offset mapping that represents a specific field. */ @HashCodeAndEqualsPlugin.Enhance public static class Resolved extends ForFieldHandle { /** * The accessed field. */ private final FieldDescription fieldDescription; /** * Creates a resolved offset mapping for a field handle. * * @param access The access type of the represented handle. * @param fieldDescription The accessed field. */ public Resolved(Access access, FieldDescription fieldDescription) { super(access); this.fieldDescription = fieldDescription; } @Override @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.") protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) { throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType); } else if (!fieldDescription.isVisibleTo(instrumentedType)) { throw new IllegalStateException("Cannot access " + fieldDescription + " from " + instrumentedType); } return fieldDescription; } /** * A factory that binds a field handle. * * @param The annotation type this factory binds. */ @HashCodeAndEqualsPlugin.Enhance public static class Factory implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * The field to be bound. */ private final FieldDescription fieldDescription; /** * The access type of the represented handle. */ private final Access access; /** * Creates a new factory for binding a specific field handle. * * @param annotationType The annotation type. * @param fieldDescription The field to bind. * @param access The access type of the represented handle. */ public Factory(Class annotationType, FieldDescription fieldDescription, Access access) { this.annotationType = annotationType; this.fieldDescription = fieldDescription; this.access = access; } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) { throw new IllegalStateException("Cannot assign method handle to " + target); } return new Resolved(access, fieldDescription); } } } } /** * An offset mapping for the {@link Advice.Origin} annotation. */ @HashCodeAndEqualsPlugin.Enhance 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; case Renderer.ForPropertyName.SYMBOL: renderers.add(Renderer.ForPropertyName.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); } } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { 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'; /** * {@inheritDoc} */ 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'; /** * {@inheritDoc} */ 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'; /** * {@inheritDoc} */ 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'; /** * {@inheritDoc} */ 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'; /** * {@inheritDoc} */ 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; /** * {@inheritDoc} */ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return instrumentedMethod.toString(); } } /** * A renderer for a constant value. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return value; } } /** * A renderer for a property name. */ enum ForPropertyName implements Renderer { /** * The singleton instance. */ INSTANCE; /** * The signature symbol. */ public static final char SYMBOL = 'p'; /** * {@inheritDoc} */ public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(instrumentedMethod); } } } /** * A factory for a method origin. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * A description of the {@link Origin#value()} method. */ private static final MethodDescription.InDefinedShape ORIGIN_VALUE = TypeDescription.ForLoadedType.of(Origin.class) .getDeclaredMethods() .filter(named("value")) .getOnly(); /** * {@inheritDoc} */ public Class getAnnotationType() { return Origin.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (target.getType().asErasure().represents(Class.class)) { return ForInstrumentedType.INSTANCE; } else if (target.getType().asErasure().represents(Method.class)) { return ForInstrumentedMethod.METHOD; } else if (target.getType().asErasure().represents(Constructor.class)) { return ForInstrumentedMethod.CONSTRUCTOR; } else if (JavaType.EXECUTABLE.getTypeStub().equals(target.getType().asErasure())) { return ForInstrumentedMethod.EXECUTABLE; } else if (JavaType.METHOD_HANDLE.getTypeStub().equals(target.getType().asErasure())) { return ForInstrumentedMethod.METHOD_HANDLE; } else if (JavaType.METHOD_TYPE.getTypeStub().equals(target.getType().asErasure())) { return ForInstrumentedMethod.METHOD_TYPE; } else if (JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().equals(target.getType().asErasure())) { return new OffsetMapping.ForStackManipulation(MethodInvocation.lookup(), JavaType.METHOD_HANDLES_LOOKUP.getTypeStub().asGenericType(), target.getType(), Assigner.Typing.STATIC); } else if (target.getType().asErasure().isAssignableFrom(String.class)) { return ForOrigin.parse(annotation.getValue(ORIGIN_VALUE).resolve(String.class)); } else { throw new IllegalStateException("Non-supported type " + target.getType() + " for @Origin annotation"); } } } } /** * An offset mapping for assigning a method handle that invokes the instrumented method. */ enum ForSelfCallHandle implements OffsetMapping { /** * A bound assignment that invokes the instrumented method as is. */ BOUND { @Override protected StackManipulation decorate(MethodDescription methodDescription, StackManipulation stackManipulation) { List stackManipulations = new ArrayList(1 + (methodDescription.isStatic() ? 0 : 2) + methodDescription.getParameters().size() * 3); stackManipulations.add(stackManipulation); if (!methodDescription.isStatic()) { stackManipulations.add(MethodVariableAccess.loadThis()); stackManipulations.add(MethodInvocation.invoke(new MethodDescription.Latent(JavaType.METHOD_HANDLE.getTypeStub(), new MethodDescription.Token("bindTo", Opcodes.ACC_PUBLIC, JavaType.METHOD_HANDLE.getTypeStub().asGenericType(), new TypeList.Generic.Explicit(TypeDefinition.Sort.describe(Object.class)))))); } if (!methodDescription.getParameters().isEmpty()) { List values = new ArrayList(methodDescription.getParameters().size()); for (ParameterDescription parameterDescription : methodDescription.getParameters()) { values.add(parameterDescription.getType().isPrimitive() ? new StackManipulation.Compound(MethodVariableAccess.load(parameterDescription), Assigner.DEFAULT.assign(parameterDescription.getType(), parameterDescription.getType().asErasure().asBoxed().asGenericType(), Assigner.Typing.STATIC)) : MethodVariableAccess.load(parameterDescription)); } stackManipulations.add(IntegerConstant.forValue(0)); stackManipulations.add(ArrayFactory.forType(TypeDescription.ForLoadedType.of(Object.class).asGenericType()).withValues(values)); stackManipulations.add( MethodInvocation.invoke(new MethodDescription.Latent(JavaType.METHOD_HANDLES.getTypeStub(), new MethodDescription.Token("insertArguments", Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, JavaType.METHOD_HANDLE.getTypeStub().asGenericType(), new TypeList.Generic.Explicit(JavaType.METHOD_HANDLE.getTypeStub(), TypeDefinition.Sort.describe(int.class), TypeDefinition.Sort.describe(Object[].class)))))); } return new StackManipulation.Compound(stackManipulations); } }, /** * An unbound self call handle which requires manual assignment of parameters. */ UNBOUND { @Override protected StackManipulation decorate(MethodDescription methodDescription, StackManipulation stackManipulation) { return stackManipulation; } }; /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { if (!instrumentedMethod.isMethod()) { throw new IllegalStateException(); } StackManipulation stackManipulation = (instrumentedMethod.isStatic() ? JavaConstant.MethodHandle.of(instrumentedMethod.asDefined()) : JavaConstant.MethodHandle.ofSpecial(instrumentedMethod.asDefined(), instrumentedType)).toStackManipulation(); return new Target.ForStackManipulation(decorate(instrumentedMethod, stackManipulation)); } /** * Resolves a stack manipulation. * * @param methodDescription The method description being represented. * @param stackManipulation The stack manipulation for the current method handle. * @return The decorated stack manipulation. */ protected abstract StackManipulation decorate(MethodDescription methodDescription, StackManipulation stackManipulation); /** * A factory for a self call method handle. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * The {@code bound} property of the {@link SelfCallHandle} annotation. */ private static final MethodDescription.InDefinedShape SELF_CALL_HANDLE_BOUND = TypeDescription.ForLoadedType.of(SelfCallHandle.class) .getDeclaredMethods() .filter(named("bound")) .getOnly(); /** * {@inheritDoc} */ public Class getAnnotationType() { return SelfCallHandle.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().asErasure().isAssignableFrom(JavaType.METHOD_HANDLE.getTypeStub())) { throw new IllegalStateException("Cannot assign a MethodHandle to " + target); } return annotation.getValue(SELF_CALL_HANDLE_BOUND).resolve(Boolean.class) ? ForSelfCallHandle.BOUND : ForSelfCallHandle.UNBOUND; } } } /** * An offset mapping for a parameter where assignments are fully ignored and that always return the parameter type's default value. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { 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; /** * {@inheritDoc} */ public Class getAnnotationType() { return Unused.class; } /** * {@inheritDoc} */ 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 value or {@code null} if the return type is not primitive or {@code void}. */ enum ForStubValue implements OffsetMapping, Factory { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { return new Target.ForDefaultValue.ReadOnly(instrumentedMethod.getReturnType(), assigner.assign(instrumentedMethod.getReturnType(), TypeDescription.Generic.OfNonGenericType.ForLoadedType.of(Object.class), Assigner.Typing.DYNAMIC)); } /** * {@inheritDoc} */ public Class getAnnotationType() { return StubValue.class; } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance 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 annotation The represented annotation. */ protected ForEnterValue(TypeDescription.Generic target, TypeDescription.Generic enterType, AnnotationDescription.Loadable annotation) { this(target, enterType, annotation.getValue(Factory.ENTER_READ_ONLY).resolve(Boolean.class), annotation.getValue(Factory.ENTER_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class)); } /** * 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { 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, argumentHandler.enter(), 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, argumentHandler.enter(), readAssignment, writeAssignment); } } /** * A factory for creating a {@link ForEnterValue} offset mapping. */ @HashCodeAndEqualsPlugin.Enhance protected static class Factory implements OffsetMapping.Factory { /** * A description of the {@link Argument#readOnly()} method. */ private static final MethodDescription.InDefinedShape ENTER_READ_ONLY; /** * A description of the {@link Argument#typing()} method. */ private static final MethodDescription.InDefinedShape ENTER_TYPING; static { MethodList methods = TypeDescription.ForLoadedType.of(Enter.class).getDeclaredMethods(); ENTER_READ_ONLY = methods.filter(named("readOnly")).getOnly(); ENTER_TYPING = methods.filter(named("typing")).getOnly(); } /** * The supplied type of the enter advice. */ 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; } /** * Creates a new factory for creating a {@link ForEnterValue} offset mapping. * * @param typeDefinition The supplied type of the enter advice. * @return An appropriate offset mapping factory. */ protected static OffsetMapping.Factory of(TypeDefinition typeDefinition) { return typeDefinition.represents(void.class) ? new Illegal(Enter.class) : new Factory(typeDefinition); } /** * {@inheritDoc} */ public Class getAnnotationType() { return Enter.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(ENTER_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { return new ForEnterValue(target.getType(), enterType.asGenericType(), annotation); } } } } /** * An offset mapping that provides access to the value that is returned by the exit advice. */ @HashCodeAndEqualsPlugin.Enhance class ForExitValue implements OffsetMapping { /** * The represented target type. */ private final TypeDescription.Generic target; /** * The exit type. */ private final TypeDescription.Generic exitType; /** * {@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 exit type. * * @param target The represented target type. * @param exitType The exit type. * @param annotation The represented annotation. */ protected ForExitValue(TypeDescription.Generic target, TypeDescription.Generic exitType, AnnotationDescription.Loadable annotation) { this(target, exitType, annotation.getValue(Factory.EXIT_READ_ONLY).resolve(Boolean.class), annotation.getValue(Factory.EXIT_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class)); } /** * Creates a new offset mapping for the enter type. * * @param target The represented target type. * @param exitType The exit type. * @param readOnly {@code true} if the annotated value is read-only. * @param typing The typing to apply. */ public ForExitValue(TypeDescription.Generic target, TypeDescription.Generic exitType, boolean readOnly, Assigner.Typing typing) { this.target = target; this.exitType = exitType; this.readOnly = readOnly; this.typing = typing; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { StackManipulation readAssignment = assigner.assign(exitType, target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + exitType + " to " + target); } else if (readOnly) { return new Target.ForVariable.ReadOnly(target, argumentHandler.exit(), readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, exitType, typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to " + exitType); } return new Target.ForVariable.ReadWrite(target, argumentHandler.exit(), readAssignment, writeAssignment); } } /** * A factory for creating a {@link ForExitValue} offset mapping. */ @HashCodeAndEqualsPlugin.Enhance protected static class Factory implements OffsetMapping.Factory { /** * A description of the {@link Exit#readOnly()} method. */ private static final MethodDescription.InDefinedShape EXIT_READ_ONLY; /** * A description of the {@link Exit#typing()} method. */ private static final MethodDescription.InDefinedShape EXIT_TYPING; /* * Resolves annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(Exit.class).getDeclaredMethods(); EXIT_READ_ONLY = methods.filter(named("readOnly")).getOnly(); EXIT_TYPING = methods.filter(named("typing")).getOnly(); } /** * The supplied type of the exit advice. */ private final TypeDefinition exitType; /** * Creates a new factory for creating a {@link ForExitValue} offset mapping. * * @param exitType The supplied type of the exit advice. */ protected Factory(TypeDefinition exitType) { this.exitType = exitType; } /** * Creates a new factory for creating a {@link ForExitValue} offset mapping. * * @param typeDefinition The supplied type of the enter method. * @return An appropriate offset mapping factory. */ protected static OffsetMapping.Factory of(TypeDefinition typeDefinition) { return typeDefinition.represents(void.class) ? new Illegal(Exit.class) : new Factory(typeDefinition); } /** * {@inheritDoc} */ public Class getAnnotationType() { return Exit.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(EXIT_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { return new ForExitValue(target.getType(), exitType.asGenericType(), annotation); } } } } /** * An offset mapping that provides access to a named local variable that is declared by the advice methods via {@link Local}. */ @HashCodeAndEqualsPlugin.Enhance class ForLocalValue implements OffsetMapping { /** * The variable's target type. */ private final TypeDescription.Generic target; /** * The local variable's type. */ private final TypeDescription.Generic localType; /** * The local variable's name. */ private final String name; /** * Creates an offset mapping for a local variable that is declared by the advice methods via {@link Local}. * * @param target The variable's target type. * @param localType The local variable's type. * @param name The local variable's name. */ public ForLocalValue(TypeDescription.Generic target, TypeDescription.Generic localType, String name) { this.target = target; this.localType = localType; this.name = name; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { StackManipulation readAssignment = assigner.assign(localType, target, Assigner.Typing.STATIC); StackManipulation writeAssignment = assigner.assign(target, localType, Assigner.Typing.STATIC); if (!readAssignment.isValid() || !writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + localType + " to " + target); } else { return new Target.ForVariable.ReadWrite(target, argumentHandler.named(name), readAssignment, writeAssignment); } } /** * A factory for an offset mapping for a local variable that is declared by the advice methods via {@link Local}. */ @HashCodeAndEqualsPlugin.Enhance protected static class Factory implements OffsetMapping.Factory { /** * A description of the {@link Local#value()} method. */ protected static final MethodDescription.InDefinedShape LOCAL_VALUE = TypeDescription.ForLoadedType.of(Local.class) .getDeclaredMethods() .filter(named("value")) .getOnly(); /** * The mapping of type names to their type that are available. */ private final Map namedTypes; /** * Creates a factory for a {@link Local} variable mapping. * * @param namedTypes The mapping of type names to their type that are available. */ protected Factory(Map namedTypes) { this.namedTypes = namedTypes; } /** * {@inheritDoc} */ public Class getAnnotationType() { return Local.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { String name = annotation.getValue(LOCAL_VALUE).resolve(String.class); TypeDefinition namedType = namedTypes.get(name); if (namedType == null) { throw new IllegalStateException("Named local variable is unknown: " + name); } return new ForLocalValue(target.getType(), namedType.asGenericType(), name); } } } /** * An offset mapping that provides access to the value that is returned by the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance 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, AnnotationDescription.Loadable annotation) { this(target, annotation.getValue(Factory.RETURN_READ_ONLY).resolve(Boolean.class), annotation.getValue(Factory.RETURN_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class)); } /** * 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { 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(), argumentHandler.returned(), 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(), argumentHandler.returned(), readAssignment, writeAssignment); } } /** * A factory for creating a {@link ForReturnValue} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * A description of the {@link Return#readOnly()} method. */ private static final MethodDescription.InDefinedShape RETURN_READ_ONLY; /** * A description of the {@link Return#typing()} method. */ private static final MethodDescription.InDefinedShape RETURN_TYPING; /* * Resolves the annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(Return.class).getDeclaredMethods(); RETURN_READ_ONLY = methods.filter(named("readOnly")).getOnly(); RETURN_TYPING = methods.filter(named("typing")).getOnly(); } /** * {@inheritDoc} */ public Class getAnnotationType() { return Return.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(RETURN_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot write return value for " + target + " in read-only context"); } else { return new ForReturnValue(target.getType(), annotation); } } } } /** * An offset mapping for accessing a {@link Throwable} of the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance 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, AnnotationDescription.Loadable annotation) { this(target, annotation.getValue(Factory.THROWN_READ_ONLY).resolve(Boolean.class), annotation.getValue(Factory.THROWN_TYPING).resolve(EnumerationDescription.class).load(Assigner.Typing.class)); } /** * 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { StackManipulation readAssignment = assigner.assign(TypeDescription.ForLoadedType.of(Throwable.class).asGenericType(), target, typing); if (!readAssignment.isValid()) { throw new IllegalStateException("Cannot assign Throwable to " + target); } else if (readOnly) { return new Target.ForVariable.ReadOnly(TypeDescription.ForLoadedType.of(Throwable.class), argumentHandler.thrown(), readAssignment); } else { StackManipulation writeAssignment = assigner.assign(target, TypeDescription.ForLoadedType.of(Throwable.class).asGenericType(), typing); if (!writeAssignment.isValid()) { throw new IllegalStateException("Cannot assign " + target + " to Throwable"); } return new Target.ForVariable.ReadWrite(TypeDescription.ForLoadedType.of(Throwable.class), argumentHandler.thrown(), 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; /** * A description of the {@link Thrown#readOnly()} method. */ private static final MethodDescription.InDefinedShape THROWN_READ_ONLY; /** * A description of the {@link Thrown#typing()} method. */ private static final MethodDescription.InDefinedShape THROWN_TYPING; /* * Resolves annotation properties. */ static { MethodList methods = TypeDescription.ForLoadedType.of(Thrown.class).getDeclaredMethods(); THROWN_READ_ONLY = methods.filter(named("readOnly")).getOnly(); THROWN_TYPING = methods.filter(named("typing")).getOnly(); } /** * 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 @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming annotation for exit advice.") 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; } /** * {@inheritDoc} */ public Class getAnnotationType() { return Thrown.class; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (adviceType.isDelegation() && !annotation.getValue(THROWN_READ_ONLY).resolve(Boolean.class)) { throw new IllegalStateException("Cannot use writable " + target + " on read-only parameter"); } else { return new ForThrowable(target.getType(), annotation); } } } } /** * An offset mapping for binding a stack manipulation. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { StackManipulation assignment = assigner.assign(typeDescription, targetType, typing); if (!assignment.isValid()) { throw new IllegalStateException("Cannot assign " + typeDescription + " to " + targetType); } return new Target.ForStackManipulation(new StackManipulation.Compound(stackManipulation, assignment)); } /** * A factory that binds a stack manipulation. * * @param The annotation type this factory binds. */ @HashCodeAndEqualsPlugin.Enhance 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.ForLoadedType.of(Class.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 Java constant. * * @param annotationType The annotation type. * @param constant The bound constant value. */ public Factory(Class annotationType, ConstantValue constant) { this(annotationType, constant.toStackManipulation(), constant.getTypeDescription().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}, a primitive value or a method handle or type. * * @param annotationType The annotation type. * @param value The constant value to bind or {@code null} to bind the parameter type's default value. * @param The annotation type. * @return A factory for creating an offset mapping that binds the supplied value. */ public static OffsetMapping.Factory of(Class annotationType, @MaybeNull Object value) { return value == null ? new OfDefaultValue(annotationType) : new Factory(annotationType, ConstantValue.Simple.wrap(value)); } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ 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. */ @HashCodeAndEqualsPlugin.Enhance 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 annotation 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); } } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { ConstantValue value = ConstantValue.Simple.wrapOrNull(annotation.getValue(property).resolve()); if (value == null) { throw new IllegalStateException("Property does not represent a constant value: " + property); } return new ForStackManipulation(value.toStackManipulation(), value.getTypeDescription().asGenericType(), target.getType(), Assigner.Typing.STATIC); } } /** * Uses dynamic method invocation for binding an annotated parameter to a value. * * @param The annotation type this factory binds. */ @HashCodeAndEqualsPlugin.Enhance public static class OfDynamicInvocation implements OffsetMapping.Factory { /** * The annotation type. */ private final Class annotationType; /** * The bootstrap method or constructor. */ private final MethodDescription.InDefinedShape bootstrapMethod; /** * The arguments to the bootstrap method. */ private final List arguments; /** * Creates a new factory for a dynamic invocation. * * @param annotationType The annotation type. * @param bootstrapMethod The bootstrap method or constructor. * @param arguments The arguments to the bootstrap method. */ public OfDynamicInvocation(Class annotationType, MethodDescription.InDefinedShape bootstrapMethod, List arguments) { this.annotationType = annotationType; this.bootstrapMethod = bootstrapMethod; this.arguments = arguments; } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { if (!target.getType().isInterface()) { throw new IllegalArgumentException(target.getType() + " is not an interface"); } else if (!target.getType().getInterfaces().isEmpty()) { throw new IllegalArgumentException(target.getType() + " must not extend other interfaces"); } else if (!target.getType().isPublic()) { throw new IllegalArgumentException(target.getType() + " is mot public"); } MethodList methodCandidates = target.getType().getDeclaredMethods().filter(isAbstract()); if (methodCandidates.size() != 1) { throw new IllegalArgumentException(target.getType() + " must declare exactly one abstract method"); } return new ForStackManipulation(MethodInvocation.invoke(bootstrapMethod).dynamic(methodCandidates.getOnly().getInternalName(), target.getType().asErasure(), Collections.emptyList(), arguments), target.getType(), target.getType(), Assigner.Typing.STATIC); } } } /** * An offset mapping that loads a serialized value. */ @HashCodeAndEqualsPlugin.Enhance 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; } /** * {@inheritDoc} */ public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, Sort sort) { 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. */ @HashCodeAndEqualsPlugin.Enhance 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. * @param The type of the -represented constant. * @return An appropriate offset mapping factory. */ public static OffsetMapping.Factory of(Class annotationType, U target, Class targetType) { if (!targetType.isInstance(target)) { throw new IllegalArgumentException(target + " is no instance of " + targetType); } return new Factory(annotationType, TypeDescription.ForLoadedType.of(targetType), SerializedConstant.of(target)); } /** * {@inheritDoc} */ public Class getAnnotationType() { return annotationType; } /** * {@inheritDoc} */ public OffsetMapping make(ParameterDescription.InDefinedShape target, AnnotationDescription.Loadable annotation, AdviceType adviceType) { return new ForSerializedValue(target.getType(), typeDescription, deserialization); } } } } /** * An argument handler is responsible for resolving offsets of the local variable array in the context of the applied instrumentation. */ public interface ArgumentHandler { /** * The offset of the {@code this} reference. */ int THIS_REFERENCE = 0; /** * Resolves an offset relative to an offset of the instrumented method. * * @param offset The offset to resolve. * @return The resolved offset. */ int argument(int offset); /** * Resolves the offset of the exit value of the exit advice. * * @return The offset of the exit value. */ int exit(); /** * Resolves the offset of the enter value of the enter advice. * * @return The offset of the enter value. */ int enter(); /** * Returns the offset of the local variable with the given name. * * @param name The name of the local variable being accessed. * @return The named variable's offset. */ int named(String name); /** * Resolves the offset of the returned value of the instrumented method. * * @return The offset of the returned value of the instrumented method. */ int returned(); /** * Resolves the offset of the thrown exception of the instrumented method. * * @return The offset of the thrown exception of the instrumented method. */ int thrown(); /** * An argument handler that is used for resolving the instrumented method. */ interface ForInstrumentedMethod extends ArgumentHandler { /** * Prepares this argument handler for future offset access. * * @param methodVisitor The method visitor to which to write any potential byte code. * @return The minimum stack size that is required to apply this manipulation. */ int prepare(MethodVisitor methodVisitor); /** * Binds an advice method as enter advice for this handler. * * @param adviceMethod The resolved enter advice handler. * @return The resolved argument handler for enter advice. */ ForAdvice bindEnter(MethodDescription adviceMethod); /** * Binds an advice method as exit advice for this handler. * * @param adviceMethod The resolved exit advice handler. * @param skipThrowable {@code true} if no throwable is stored. * @return The resolved argument handler for enter advice. */ ForAdvice bindExit(MethodDescription adviceMethod, boolean skipThrowable); /** * Returns {@code true} if the original arguments are copied before invoking the instrumented method. * * @return {@code true} if the original arguments are copied before invoking the instrumented method. */ boolean isCopyingArguments(); /** * Returns a list of the named types in their declared order. * * @return A list of the named types in their declared order. */ List getNamedTypes(); /** * A default implementation of an argument handler for an instrumented method. */ abstract class Default implements ForInstrumentedMethod { /** * The instrumented method. */ protected final MethodDescription instrumentedMethod; /** * The exit type or {@code void} if no exit type is defined. */ protected final TypeDefinition exitType; /** * A mapping of all available local variables by their name to their type. */ protected final SortedMap namedTypes; /** * The enter type or {@code void} if no enter type is defined. */ protected final TypeDefinition enterType; /** * Creates a new default argument handler for an instrumented method. * * @param instrumentedMethod The instrumented method. * @param exitType The exit type or {@code void} if no exit type is defined. * @param namedTypes A mapping of all available local variables by their name to their type. * @param enterType The enter type or {@code void} if no enter type is defined. */ protected Default(MethodDescription instrumentedMethod, TypeDefinition exitType, SortedMap namedTypes, TypeDefinition enterType) { this.instrumentedMethod = instrumentedMethod; this.namedTypes = namedTypes; this.exitType = exitType; this.enterType = enterType; } /** * {@inheritDoc} */ public int exit() { return instrumentedMethod.getStackSize(); } /** * {@inheritDoc} */ public int named(String name) { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.headMap(name).values()); } /** * {@inheritDoc} */ public int enter() { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()); } /** * {@inheritDoc} */ public int returned() { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize(); } /** * {@inheritDoc} */ public int thrown() { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize() + instrumentedMethod.getReturnType().getStackSize().getSize(); } /** * {@inheritDoc} */ public ForAdvice bindEnter(MethodDescription adviceMethod) { return new ForAdvice.Default.ForMethodEnter(instrumentedMethod, adviceMethod, exitType, namedTypes); } /** * {@inheritDoc} */ public ForAdvice bindExit(MethodDescription adviceMethod, boolean skipThrowable) { return new ForAdvice.Default.ForMethodExit(instrumentedMethod, adviceMethod, exitType, namedTypes, enterType, skipThrowable ? StackSize.ZERO : StackSize.SINGLE); } /** * {@inheritDoc} */ public List getNamedTypes() { List namedTypes = new ArrayList(this.namedTypes.size()); for (TypeDefinition typeDefinition : this.namedTypes.values()) { namedTypes.add(typeDefinition.asErasure()); } return namedTypes; } /** * A simple argument handler for an instrumented method. */ @HashCodeAndEqualsPlugin.Enhance protected static class Simple extends Default { /** * Creates a new simple argument handler for an instrumented method. * * @param instrumentedMethod The instrumented method. * @param exitType The exit type or {@code void} if no exit type is defined. * @param namedTypes A mapping of all available local variables by their name to their type. * @param enterType The enter type or {@code void} if no enter type is defined. */ protected Simple(MethodDescription instrumentedMethod, TypeDefinition exitType, SortedMap namedTypes, TypeDefinition enterType) { super(instrumentedMethod, exitType, namedTypes, enterType); } /** * {@inheritDoc} */ public int argument(int offset) { return offset < instrumentedMethod.getStackSize() ? offset : offset + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize(); } /** * {@inheritDoc} */ public boolean isCopyingArguments() { return false; } /** * {@inheritDoc} */ public int prepare(MethodVisitor methodVisitor) { return 0; } } /** * An argument handler for an instrumented method that copies all arguments before executing the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance protected static class Copying extends Default { /** * Creates a new copying argument handler for an instrumented method. * * @param instrumentedMethod The instrumented method. * @param exitType The exit type or {@code void} if no exit type is defined. * @param namedTypes A mapping of all available local variables by their name to their type. * @param enterType The enter type or {@code void} if no enter type is defined. */ protected Copying(MethodDescription instrumentedMethod, TypeDefinition exitType, SortedMap namedTypes, TypeDefinition enterType) { super(instrumentedMethod, exitType, namedTypes, enterType); } /** * {@inheritDoc} */ public int argument(int offset) { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize() + offset; } /** * {@inheritDoc} */ public boolean isCopyingArguments() { return true; } /** * {@inheritDoc} */ public int prepare(MethodVisitor methodVisitor) { StackSize stackSize; if (!instrumentedMethod.isStatic()) { methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize()); stackSize = StackSize.SINGLE; } else { stackSize = StackSize.ZERO; } for (ParameterDescription parameterDescription : instrumentedMethod.getParameters()) { Type type = Type.getType(parameterDescription.getType().asErasure().getDescriptor()); methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ILOAD), parameterDescription.getOffset()); methodVisitor.visitVarInsn(type.getOpcode(Opcodes.ISTORE), instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize() + parameterDescription.getOffset()); stackSize = stackSize.maximum(parameterDescription.getType().getStackSize()); } return stackSize.getSize(); } } } } /** * An argument handler that is used for resolving an advice method. */ interface ForAdvice extends ArgumentHandler { /** * Resolves an offset of the advice method. * * @param offset The offset to resolve. * @return The resolved offset. */ int mapped(int offset); /** * A default implementation for an argument handler for an advice method. */ abstract class Default implements ForAdvice { /** * The instrumented method. */ protected final MethodDescription instrumentedMethod; /** * The advice method. */ protected final MethodDescription adviceMethod; /** * The enter type or {@code void} if no enter type is defined. */ protected final TypeDefinition exitType; /** * A mapping of all available local variables by their name to their type. */ protected final SortedMap namedTypes; /** * Creates a new argument handler for an enter advice. * * @param instrumentedMethod The instrumented method. * @param adviceMethod The advice method. * @param exitType The exit type or {@code void} if no exit type is defined. * @param namedTypes A mapping of all available local variables by their name to their type. */ protected Default(MethodDescription instrumentedMethod, MethodDescription adviceMethod, TypeDefinition exitType, SortedMap namedTypes) { this.instrumentedMethod = instrumentedMethod; this.adviceMethod = adviceMethod; this.exitType = exitType; this.namedTypes = namedTypes; } /** * {@inheritDoc} */ public int argument(int offset) { return offset; } /** * {@inheritDoc} */ public int exit() { return instrumentedMethod.getStackSize(); } /** * {@inheritDoc} */ public int named(String name) { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.headMap(name).values()); } /** * {@inheritDoc} */ public int enter() { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()); } /** * An argument handler for an enter advice method. */ @HashCodeAndEqualsPlugin.Enhance protected static class ForMethodEnter extends Default { /** * Creates a new argument handler for an enter advice method. * * @param instrumentedMethod The instrumented method. * @param adviceMethod The advice method. * @param exitType The exit type or {@code void} if no exit type is defined. * @param namedTypes A mapping of all available local variables by their name to their type. */ protected ForMethodEnter(MethodDescription instrumentedMethod, MethodDescription adviceMethod, TypeDefinition exitType, SortedMap namedTypes) { super(instrumentedMethod, adviceMethod, exitType, namedTypes); } /** * {@inheritDoc} */ public int returned() { throw new IllegalStateException("Cannot resolve the return value offset during enter advice"); } /** * {@inheritDoc} */ public int thrown() { throw new IllegalStateException("Cannot resolve the thrown value offset during enter advice"); } /** * {@inheritDoc} */ public int mapped(int offset) { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) - adviceMethod.getStackSize() + offset; } } /** * An argument handler for an exit advice method. */ @HashCodeAndEqualsPlugin.Enhance protected static class ForMethodExit extends Default { /** * The enter type or {@code void} if no enter type is defined. */ private final TypeDefinition enterType; /** * The stack size of a possibly stored throwable. */ private final StackSize throwableSize; /** * Creates a new argument handler for an exit advice method. * * @param instrumentedMethod The instrumented method. * @param adviceMethod The advice method. * @param exitType The exit type or {@code void} if no exit type is defined. * @param namedTypes A mapping of all available local variables by their name to their type. * @param enterType The enter type or {@code void} if no enter type is defined. * @param throwableSize The stack size of a possibly stored throwable. */ protected ForMethodExit(MethodDescription instrumentedMethod, MethodDescription adviceMethod, TypeDefinition exitType, SortedMap namedTypes, TypeDefinition enterType, StackSize throwableSize) { super(instrumentedMethod, adviceMethod, exitType, namedTypes); this.enterType = enterType; this.throwableSize = throwableSize; } /** * {@inheritDoc} */ public int returned() { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize(); } /** * {@inheritDoc} */ public int thrown() { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize() + instrumentedMethod.getReturnType().getStackSize().getSize(); } /** * {@inheritDoc} */ public int mapped(int offset) { return instrumentedMethod.getStackSize() + exitType.getStackSize().getSize() + StackSize.of(namedTypes.values()) + enterType.getStackSize().getSize() + instrumentedMethod.getReturnType().getStackSize().getSize() + throwableSize.getSize() - adviceMethod.getStackSize() + offset; } } } } /** * A factory for creating an argument handler. */ enum Factory { /** * A factory for creating a simple argument handler. */ SIMPLE { @Override protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod, TypeDefinition enterType, TypeDefinition exitType, SortedMap namedTypes) { return new ForInstrumentedMethod.Default.Simple(instrumentedMethod, exitType, namedTypes, enterType); } }, /** * A factory for creating an argument handler that copies all arguments before executing the instrumented method. */ COPYING { @Override protected ForInstrumentedMethod resolve(MethodDescription instrumentedMethod, TypeDefinition enterType, TypeDefinition exitType, SortedMap namedTypes) { return new ForInstrumentedMethod.Default.Copying(instrumentedMethod, exitType, namedTypes, enterType); } }; /** * Creates an argument handler. * * @param instrumentedMethod The instrumented method. * @param enterType The enter type or {@code void} if no such type is defined. * @param exitType The exit type or {@code void} if no exit type is defined. * @param namedTypes A mapping of all available local variables by their name to their type. * @return An argument handler for the instrumented method. */ protected abstract ForInstrumentedMethod resolve(MethodDescription instrumentedMethod, TypeDefinition enterType, TypeDefinition exitType, SortedMap namedTypes); } } /** *

* A post processor for advice methods that is invoked after advice is executed. A post processor * is invoked after the instrumented method and only after a regular completion of the method. When * invoked, the advice method's return value is stored in the local variable array. Upon completion, * the local variable array must still be intact and the stack must be empty. A frame is added * subsequently to the post processor's execution, making it feasible to add a jump instruction to the * end of the method after which no further byte code instructions must be issued. This also applies * to compound post processors. If a post processor emits a frame as its last instruction, it should * yield a NOP instruction to avoid that subsequent code starts with a frame. *

*

* Important: A post processor is triggered after the suppression handler. Exceptions triggered * by post processing code will therefore cause those exceptions to be propagated unless the post * processor configures explicit exception handling. *

*/ public interface PostProcessor { /** * Resolves this post processor for a given instrumented method. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param assigner The assigner to use. * @param argumentHandler The argument handler to use. * @param stackMapFrameHandler The argument handler for the instrumented method. * @param exceptionHandler The exception handler that is resolved for the instrumented method. * @return The stack manipulation to apply. */ StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, StackMapFrameHandler.ForPostProcessor stackMapFrameHandler, StackManipulation exceptionHandler); /** * A factory for creating a {@link PostProcessor}. */ interface Factory { /** * Creates a post processor for a given advice method. * * @param advice The advice method to create the post processor for. * @param exit {@code true} if the advice is exit advice. * @return The created post processor. */ PostProcessor make(MethodDescription.InDefinedShape advice, boolean exit); /** * A compound factory for a post processor. */ @HashCodeAndEqualsPlugin.Enhance class Compound implements Factory { /** * The represented post processor factories. */ private final List factories; /** * Creates a compound post processor factory. * * @param factory The represented post processor factories. */ public Compound(Factory... factory) { this(Arrays.asList(factory)); } /** * Creates a compound post processor factory. * * @param factories The represented post processor factories. */ public Compound(List factories) { this.factories = new ArrayList(); for (Factory factory : factories) { if (factory instanceof Compound) { this.factories.addAll(((Compound) factory).factories); } else if (!(factory instanceof NoOp)) { this.factories.add(factory); } } } /** * {@inheritDoc} */ public PostProcessor make(MethodDescription.InDefinedShape advice, boolean exit) { List postProcessors = new ArrayList(factories.size()); for (Factory factory : factories) { postProcessors.add(factory.make(advice, exit)); } return new PostProcessor.Compound(postProcessors); } } } /** * A non-operational advice post processor. */ enum NoOp implements PostProcessor, Factory { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, StackMapFrameHandler.ForPostProcessor stackMapFrameHandler, StackManipulation exceptionHandler) { return StackManipulation.Trivial.INSTANCE; } /** * {@inheritDoc} */ public PostProcessor make(MethodDescription.InDefinedShape advice, boolean exit) { return this; } } /** * A compound post processor. */ @HashCodeAndEqualsPlugin.Enhance class Compound implements PostProcessor { /** * The represented post processors. */ private final List postProcessors; /** * Creates a new compound post processor. * * @param postProcessors The represented post processors. */ protected Compound(List postProcessors) { this.postProcessors = postProcessors; } /** * {@inheritDoc} */ public StackManipulation resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, ArgumentHandler argumentHandler, StackMapFrameHandler.ForPostProcessor stackMapFrameHandler, StackManipulation exceptionHandler) { List stackManipulations = new ArrayList(postProcessors.size()); for (PostProcessor postProcessor : postProcessors) { stackManipulations.add(postProcessor.resolve(instrumentedType, instrumentedMethod, assigner, argumentHandler, stackMapFrameHandler, exceptionHandler)); } return new StackManipulation.Compound(stackManipulations); } } } /** * Materializes an advice invocation within a delegation. */ protected interface Delegator { /** * Materializes an invocation. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @return An appropriate stack manipulation which needs to consume all arguments for the advice * method and needs to provide a compatible return type. */ StackManipulation apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod); /** * A factory for creating a {@link Delegator}. */ interface Factory { /** * Resolves a delegator. * * @param adviceMethod The advice method. * @param exit {@code true} if the advice is applied as exit advice. * @return An appropriate delegator. */ Delegator make(MethodDescription.InDefinedShape adviceMethod, boolean exit); } /** * Invokes an advice method using a regular method call. */ @HashCodeAndEqualsPlugin.Enhance class ForRegularInvocation implements Delegator { /** * The advice method. */ private final MethodDescription.InDefinedShape adviceMethod; /** * Creates a delegator for a regular invocation. * * @param adviceMethod The advice method. */ protected ForRegularInvocation(MethodDescription.InDefinedShape adviceMethod) { this.adviceMethod = adviceMethod; } /** * {@inheritDoc} */ public StackManipulation apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return MethodInvocation.invoke(adviceMethod); } /** * A factory for a regular method invocation delegator. */ protected enum Factory implements Delegator.Factory { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Delegator make(MethodDescription.InDefinedShape adviceMethod, boolean exit) { return new ForRegularInvocation(adviceMethod); } } } /** * Invokes an advice method using a dynamic method call. */ @HashCodeAndEqualsPlugin.Enhance class ForDynamicInvocation implements Delegator { /** * The bootstrap method. */ private final MethodDescription.InDefinedShape bootstrapMethod; /** * The advice method. */ private final MethodDescription.InDefinedShape adviceMethod; /** * A resolver to provide the arguments to the bootstrap method. */ private final BootstrapArgumentResolver resolver; /** * Creates a delegator for a dynamic method invocation. * * @param bootstrapMethod The bootstrap method. * @param adviceMethod The advice method. * @param resolver A resolver to provide the arguments to the bootstrap method. */ protected ForDynamicInvocation(MethodDescription.InDefinedShape bootstrapMethod, MethodDescription.InDefinedShape adviceMethod, BootstrapArgumentResolver resolver) { this.bootstrapMethod = bootstrapMethod; this.adviceMethod = adviceMethod; this.resolver = resolver; } /** * Creates a new dynamic invocation delegator. * * @param bootstrapMethod The bootstrap method or constructor. * @param resolverFactory A resolver factory to provide the arguments to the bootstrap method. * @return An appropriate delegator. */ protected static Delegator.Factory of(MethodDescription.InDefinedShape bootstrapMethod, BootstrapArgumentResolver.Factory resolverFactory) { if (!bootstrapMethod.isInvokeBootstrap()) { throw new IllegalArgumentException("Not a suitable bootstrap target: " + bootstrapMethod); } return new Factory(bootstrapMethod, resolverFactory); } /** * {@inheritDoc} */ public StackManipulation apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { List constants = resolver.resolve(instrumentedType, instrumentedMethod); if (!bootstrapMethod.isInvokeBootstrap(TypeList.Explicit.of(constants))) { throw new IllegalStateException("Cannot invoke " + bootstrapMethod + " with arguments: " + constants); } return MethodInvocation.invoke(bootstrapMethod).dynamic(adviceMethod.getInternalName(), adviceMethod.getReturnType().asErasure(), adviceMethod.getParameters().asTypeList().asErasures(), constants); } /** * A factory for creating a dynamic invocation dispatcher. */ @HashCodeAndEqualsPlugin.Enhance protected static class Factory implements Delegator.Factory { /** * The bootstrap method. */ private final MethodDescription.InDefinedShape bootstrapMethod; /** * A resolver factory to provide the arguments to the bootstrap method. */ private final BootstrapArgumentResolver.Factory resolverFactory; /** * Creates a factory for a dynamic invocation dispatcher. * * @param bootstrapMethod The bootstrap method. * @param resolverFactory A resolver factory to provide the arguments to the bootstrap method. */ protected Factory(MethodDescription.InDefinedShape bootstrapMethod, BootstrapArgumentResolver.Factory resolverFactory) { this.bootstrapMethod = bootstrapMethod; this.resolverFactory = resolverFactory; } /** * {@inheritDoc} */ public Delegator make(MethodDescription.InDefinedShape adviceMethod, boolean exit) { return new ForDynamicInvocation(bootstrapMethod, adviceMethod, resolverFactory.resolve(adviceMethod, exit)); } } } } /** * A resolver for the arguments that are provided to a bootstrap method if dynamic dispatch is used. */ public interface BootstrapArgumentResolver { /** * Resolves the constants that are provided as arguments to the bootstrap methods. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @return A list of constants to supply as arguments to the bootstrap method. */ List resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod); /** * A factory for creating a {@link BootstrapArgumentResolver}. */ interface Factory { /** * Creates a bootstrap argument resolver for a given advice. * * @param adviceMethod The advice method. * @param exit {@code true} if the method is bound as exit advice. * @return An appropriate bootstrap argument resolver. */ BootstrapArgumentResolver resolve(MethodDescription.InDefinedShape adviceMethod, boolean exit); } /** * An argument resolver that supplies a default selection of arguments. The explicitly resolved constant values are: *
    *
  • A {@link String} of the target's binary class name.
  • *
  • A {@code int} with value {@code 0} for an enter advice and {code 1} for an exist advice.
  • *
  • A {@link Class} representing the class implementing the instrumented method.
  • *
  • A {@link String} with the name of the instrumented method.
  • *
  • A {@code java.lang.invoke.MethodHandle} representing the instrumented method unless the target is the type's static initializer.
  • *
*/ @HashCodeAndEqualsPlugin.Enhance class ForDefaultValues implements BootstrapArgumentResolver { /** * The advice method. */ private final MethodDescription.InDefinedShape adviceMethod; /** * {@code true} if the advice is applied as exit advice. */ private final boolean exit; /** * Creates a bootstrap argument resolver with default values. * * @param adviceMethod The advice method. * @param exit {@code true} if the advice is applied as exit advice. */ protected ForDefaultValues(MethodDescription.InDefinedShape adviceMethod, boolean exit) { this.adviceMethod = adviceMethod; this.exit = exit; } /** * {@inheritDoc} */ public List resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) { if (instrumentedMethod.isTypeInitializer()) { return Arrays.asList(net.bytebuddy.utility.JavaConstant.Simple.ofLoaded(adviceMethod.getDeclaringType().getName()), net.bytebuddy.utility.JavaConstant.Simple.ofLoaded(exit ? 1 : 0), net.bytebuddy.utility.JavaConstant.Simple.of(instrumentedType), net.bytebuddy.utility.JavaConstant.Simple.ofLoaded(instrumentedMethod.getInternalName())); } else { return Arrays.asList(net.bytebuddy.utility.JavaConstant.Simple.ofLoaded(adviceMethod.getDeclaringType().getName()), net.bytebuddy.utility.JavaConstant.Simple.ofLoaded(exit ? 1 : 0), net.bytebuddy.utility.JavaConstant.Simple.of(instrumentedType), net.bytebuddy.utility.JavaConstant.Simple.ofLoaded(instrumentedMethod.getInternalName()), net.bytebuddy.utility.JavaConstant.MethodHandle.of(instrumentedMethod.asDefined())); } } /** * A factory for creating a {@link ForDefaultValues}. */ public enum Factory implements BootstrapArgumentResolver.Factory { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public BootstrapArgumentResolver resolve(MethodDescription.InDefinedShape adviceMethod, boolean exit) { return new ForDefaultValues(adviceMethod, exit); } } } } /** * 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; /** * 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); /** * 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 enter advice. * * @param adviceMethod The method representing the enter advice. * @return A method size handler for the enter advice. */ ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod); /** * Binds the method size handler for the exit advice. * * @param adviceMethod The method representing the exit advice. * @return A method size handler for the exit advice. */ ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod); /** * 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 { /** * Requires additional padding for the operand stack that is required for this advice's execution. * * @param stackSizePadding The required padding. */ void requireStackSizePadding(int stackSizePadding); /** * Requires additional padding for the local variable array that is required for this advice's execution. * * @param localVariableLengthPadding The required padding. */ void requireLocalVariableLengthPadding(int localVariableLengthPadding); /** * 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); } /** * A non-operational method size handler. */ enum NoOp implements ForInstrumentedMethod, ForAdvice { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) { return this; } /** * {@inheritDoc} */ public ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { return this; } /** * {@inheritDoc} */ public int compoundStackSize(int stackSize) { return UNDEFINED_SIZE; } /** * {@inheritDoc} */ public int compoundLocalVariableLength(int localVariableLength) { return UNDEFINED_SIZE; } /** * {@inheritDoc} */ public void requireStackSize(int stackSize) { /* do nothing */ } /** * {@inheritDoc} */ public void requireLocalVariableLength(int localVariableLength) { /* do nothing */ } /** * {@inheritDoc} */ public void requireStackSizePadding(int stackSizePadding) { /* do nothing */ } /** * {@inheritDoc} */ public void requireLocalVariableLengthPadding(int localVariableLengthPadding) { /* do nothing */ } /** * {@inheritDoc} */ public void recordMaxima(int stackSize, int localVariableLength) { /* do nothing */ } } /** * A default implementation for a method size handler. */ abstract class Default implements MethodSizeHandler.ForInstrumentedMethod { /** * The instrumented method. */ protected final MethodDescription instrumentedMethod; /** * A list of virtual method arguments that are explicitly added before any code execution. */ protected final List initialTypes; /** * A list of virtual method arguments that are available before the instrumented method is executed. */ protected final List preMethodTypes; /** * A list of virtual method arguments that are available after the instrumented method has completed. */ protected final List postMethodTypes; /** * The maximum stack size required by a visited advice method. */ protected int stackSize; /** * The maximum length of the local variable array required by a visited advice method. */ protected int localVariableLength; /** * Creates a new default meta data handler that recomputes the space requirements of an instrumented method. * * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. */ protected Default(MethodDescription instrumentedMethod, List initialTypes, List preMethodTypes, List postMethodTypes) { this.instrumentedMethod = instrumentedMethod; this.initialTypes = initialTypes; this.preMethodTypes = preMethodTypes; this.postMethodTypes = postMethodTypes; } /** * Creates a method size handler applicable for the given instrumented method. * * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param copyArguments {@code true} if the original arguments are copied before invoking the instrumented method. * @param writerFlags The flags supplied to the ASM class writer. * @return An appropriate method size handler. */ protected static MethodSizeHandler.ForInstrumentedMethod of(MethodDescription instrumentedMethod, List initialTypes, List preMethodTypes, List postMethodTypes, boolean copyArguments, int writerFlags) { if ((writerFlags & (ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES)) != 0) { return NoOp.INSTANCE; } else if (copyArguments) { return new WithCopiedArguments(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes); } else { return new WithRetainedArguments(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes); } } /** * {@inheritDoc} */ public MethodSizeHandler.ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) { return new ForAdvice(adviceMethod, instrumentedMethod.getStackSize() + StackSize.of(initialTypes)); } /** * {@inheritDoc} */ public void requireStackSize(int stackSize) { Default.this.stackSize = Math.max(this.stackSize, stackSize); } /** * {@inheritDoc} */ public void requireLocalVariableLength(int localVariableLength) { this.localVariableLength = Math.max(this.localVariableLength, localVariableLength); } /** * {@inheritDoc} */ public int compoundStackSize(int stackSize) { return Math.max(this.stackSize, stackSize); } /** * {@inheritDoc} */ public int compoundLocalVariableLength(int localVariableLength) { return Math.max(this.localVariableLength, localVariableLength + StackSize.of(postMethodTypes) + StackSize.of(initialTypes) + StackSize.of(preMethodTypes)); } /** * A method size handler that expects that the original arguments are retained. */ protected static class WithRetainedArguments extends Default { /** * Creates a new default method size handler that expects that the original arguments are retained. * * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. */ protected WithRetainedArguments(MethodDescription instrumentedMethod, List initialTypes, List preMethodTypes, List postMethodTypes) { super(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes); } /** * {@inheritDoc} */ public MethodSizeHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { return new ForAdvice(adviceMethod, instrumentedMethod.getStackSize() + StackSize.of(postMethodTypes) + StackSize.of(initialTypes) + StackSize.of(preMethodTypes)); } /** * {@inheritDoc} */ public int compoundLocalVariableLength(int localVariableLength) { return Math.max(this.localVariableLength, localVariableLength + StackSize.of(postMethodTypes) + StackSize.of(initialTypes) + StackSize.of(preMethodTypes)); } } /** * A method size handler that expects that the original arguments were copied. */ protected static class WithCopiedArguments extends Default { /** * Creates a new default method size handler that expects the original arguments to be copied. * * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. */ protected WithCopiedArguments(MethodDescription instrumentedMethod, List initialTypes, List preMethodTypes, List postMethodTypes) { super(instrumentedMethod, initialTypes, preMethodTypes, postMethodTypes); } /** * {@inheritDoc} */ public MethodSizeHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { return new ForAdvice(adviceMethod, 2 * instrumentedMethod.getStackSize() + StackSize.of(initialTypes) + StackSize.of(preMethodTypes) + StackSize.of(postMethodTypes)); } /** * {@inheritDoc} */ public int compoundLocalVariableLength(int localVariableLength) { return Math.max(this.localVariableLength, localVariableLength + instrumentedMethod.getStackSize() + StackSize.of(postMethodTypes) + StackSize.of(initialTypes) + StackSize.of(preMethodTypes)); } } /** * A method size handler for an advice method. */ protected class ForAdvice implements MethodSizeHandler.ForAdvice { /** * The advice method. */ private final MethodDescription.InDefinedShape adviceMethod; /** * The base of the local variable length that is implied by the method instrumentation prior to applying this advice method. */ private final int baseLocalVariableLength; /** * The additional padding to apply to the operand stack. */ private int stackSizePadding; /** * The additional padding to apply to the local variable array. */ private int localVariableLengthPadding; /** * Creates a default method size handler for an advice method. * * @param adviceMethod The advice method. * @param baseLocalVariableLength The base of the local variable length that is implied by the method instrumentation * prior to applying this advice method. */ protected ForAdvice(MethodDescription.InDefinedShape adviceMethod, int baseLocalVariableLength) { this.adviceMethod = adviceMethod; this.baseLocalVariableLength = baseLocalVariableLength; } /** * {@inheritDoc} */ public void requireStackSize(int stackSize) { Default.this.requireStackSize(stackSize); } /** * {@inheritDoc} */ public void requireLocalVariableLength(int localVariableLength) { Default.this.requireLocalVariableLength(localVariableLength); } /** * {@inheritDoc} */ public void requireStackSizePadding(int stackSizePadding) { this.stackSizePadding = Math.max(this.stackSizePadding, stackSizePadding); } /** * {@inheritDoc} */ public void requireLocalVariableLengthPadding(int localVariableLengthPadding) { this.localVariableLengthPadding = Math.max(this.localVariableLengthPadding, localVariableLengthPadding); } /** * {@inheritDoc} */ public void recordMaxima(int stackSize, int localVariableLength) { Default.this.requireStackSize(stackSize + stackSizePadding); Default.this.requireLocalVariableLength(localVariableLength - adviceMethod.getStackSize() + baseLocalVariableLength + localVariableLengthPadding); } } } } /** * A handler for computing and translating stack map frames. */ public 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, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull 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. */ void injectCompletionFrame(MethodVisitor methodVisitor); /** * A stack map frame handler that can be used within a post processor. Emitting frames via this * handler is the only legal way for a post processor to produce frames. */ interface ForPostProcessor { /** * Injects a frame that represents the current state. * * @param methodVisitor The method visitor onto which to apply the stack map frame. * @param stack A list of types that are currently on the stack. */ void injectIntermediateFrame(MethodVisitor methodVisitor, List stack); } /** * A stack map frame handler for an instrumented method. */ interface ForInstrumentedMethod extends StackMapFrameHandler { /** * Binds this meta data handler for the enter advice. * * @param adviceMethod The enter advice method. * @return An appropriate meta data handler for the enter method. */ ForAdvice bindEnter(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(); /** * Injects a frame after initialization if any initialization is performed. * * @param methodVisitor The method visitor to write any frames to. */ void injectInitializationFrame(MethodVisitor methodVisitor); /** * Injects a frame before executing the instrumented method. * * @param methodVisitor The method visitor to write any frames to. */ void injectStartFrame(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. */ void injectPostCompletionFrame(MethodVisitor methodVisitor); } /** * A stack map frame handler for an advice method. */ interface ForAdvice extends StackMapFrameHandler, ForPostProcessor { /* marker interface */ } /** * A non-operational stack map frame handler. */ enum NoOp implements ForInstrumentedMethod, ForAdvice { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public StackMapFrameHandler.ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) { return this; } /** * {@inheritDoc} */ public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { return this; } /** * {@inheritDoc} */ public int getReaderHint() { return ClassReader.SKIP_FRAMES; } /** * {@inheritDoc} */ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull Object[] stack) { /* do nothing */ } /** * {@inheritDoc} */ public void injectReturnFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void injectExceptionFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void injectCompletionFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void injectInitializationFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void injectStartFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void injectPostCompletionFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void injectIntermediateFrame(MethodVisitor methodVisitor, List stack) { /* do nothing */ } } /** * A default implementation of a stack map frame handler for an instrumented method. */ abstract class Default implements ForInstrumentedMethod { /** * An empty array indicating an empty frame. */ protected static final Object[] EMPTY = new Object[0]; /** * The instrumented type. */ protected final TypeDescription instrumentedType; /** * The instrumented method. */ protected final MethodDescription instrumentedMethod; /** * A list of virtual method arguments that are explicitly added before any code execution. */ protected final List initialTypes; /** * A list of virtual arguments that are available after the enter advice method is executed. */ protected final List latentTypes; /** * A list of virtual method arguments that are available before the instrumented method is executed. */ protected final List preMethodTypes; /** * A list of virtual method arguments that are available after the instrumented method has completed. */ protected final List postMethodTypes; /** * {@code true} if the meta data handler is expected to expand its frames. */ protected final boolean expandFrames; /** * The current frame's size divergence from the original local variable array. */ protected int currentFrameDivergence; /** * Creates a new default stack map frame handler. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. */ protected Default(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, List latentTypes, List preMethodTypes, List postMethodTypes, boolean expandFrames) { this.instrumentedType = instrumentedType; this.instrumentedMethod = instrumentedMethod; this.initialTypes = initialTypes; this.latentTypes = latentTypes; this.preMethodTypes = preMethodTypes; this.postMethodTypes = postMethodTypes; 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 initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param exitAdvice {@code true} if the current advice implies exit advice. * @param copyArguments {@code true} if the original arguments are copied before invoking the instrumented method. * @param classFileVersion The instrumented type's class file version. * @param writerFlags The flags supplied to the ASM writer. * @param readerFlags The reader flags supplied to the ASM reader. * @return An appropriate stack map frame handler for an instrumented method. */ protected static ForInstrumentedMethod of(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, List latentTypes, List preMethodTypes, List postMethodTypes, boolean exitAdvice, boolean copyArguments, ClassFileVersion classFileVersion, int writerFlags, int readerFlags) { if ((writerFlags & ClassWriter.COMPUTE_FRAMES) != 0 || classFileVersion.isLessThan(ClassFileVersion.JAVA_V6)) { return NoOp.INSTANCE; } else if (!exitAdvice && initialTypes.isEmpty()) { return new Trivial(instrumentedType, instrumentedMethod, latentTypes, (readerFlags & ClassReader.EXPAND_FRAMES) != 0); } else if (copyArguments) { return new WithPreservedArguments.WithArgumentCopy(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, (readerFlags & ClassReader.EXPAND_FRAMES) != 0); } else { return new WithPreservedArguments.WithoutArgumentCopy(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, (readerFlags & ClassReader.EXPAND_FRAMES) != 0, !instrumentedMethod.isConstructor()); } } /** * {@inheritDoc} */ public StackMapFrameHandler.ForAdvice bindEnter(MethodDescription.InDefinedShape adviceMethod) { return new ForAdvice(adviceMethod, initialTypes, latentTypes, preMethodTypes, TranslationMode.ENTER, instrumentedMethod.isConstructor() ? Initialization.UNITIALIZED : Initialization.INITIALIZED); } /** * {@inheritDoc} */ public int getReaderHint() { return expandFrames ? ClassReader.EXPAND_FRAMES : AsmVisitorWrapper.NO_FLAGS; } /** * 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, List additionalTypes, int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull 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; if (currentFrameDivergence < 0) { throw new IllegalStateException(methodDescription + " dropped " + Math.abs(currentFrameDivergence) + " implicit frames"); } 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 (!Initialization.INITIALIZED.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.isStatic() ? 0 : 1) - methodDescription.getParameters().size() + (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size() + additionalTypes.size()]; int index = translationMode.copy(instrumentedType, instrumentedMethod, methodDescription, localVariable, translated); for (TypeDescription typeDescription : additionalTypes) { translated[index++] = Initialization.INITIALIZED.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); } /** * Injects a full stack map frame after the instrumented method has completed. * * @param methodVisitor The method visitor onto which to write the stack map frame. * @param initialization The initialization to apply when resolving a reference to the instance on which a non-static method is invoked. * @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, Initialization initialization, 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++] = initialization.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } for (TypeDescription typeDescription : typesInArray) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } index = 0; Object[] stackType = new Object[typesOnStack.size()]; for (TypeDescription typeDescription : typesOnStack) { stackType[index++] = Initialization.INITIALIZED.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) || Initialization.INITIALIZED.toFrame(instrumentedType).equals(frame); } }, /** * A translation mode for the enter advice that considers that the {@code this} reference might not be initialized for a constructor. */ ENTER { @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 : Initialization.INITIALIZED.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } return index; } @Override protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) { return instrumentedMethod.isConstructor() ? Opcodes.UNINITIALIZED_THIS.equals(frame) : Initialization.INITIALIZED.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++] = Initialization.INITIALIZED.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } return index; } @Override protected boolean isPossibleThisFrameValue(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Object frame) { return Initialization.INITIALIZED.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); } /** * Represents the initialization state of a stack value that can either be initialized or uninitialized. */ protected enum Initialization { /** * Represents an uninitialized frame value within a constructor before invoking the super constructor. */ UNITIALIZED { /** * {@inheritDoc} */ protected Object toFrame(TypeDescription typeDescription) { if (typeDescription.isPrimitive()) { throw new IllegalArgumentException("Cannot assume primitive uninitialized value: " + typeDescription); } return Opcodes.UNINITIALIZED_THIS; } }, /** * Represents an initialized frame value. */ INITIALIZED { /** * {@inheritDoc} */ protected 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(); } } }; /** * Initializes a frame value to its frame type. * * @param typeDescription The type being resolved. * @return The frame value. */ protected abstract Object toFrame(TypeDescription typeDescription); } /** * A trivial stack map frame handler that applies a trivial translation for the instrumented method's stack map frames. */ protected static class Trivial extends Default { /** * Creates a new stack map frame handler that applies a trivial translation for the instrumented method's stack map frames. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. */ protected Trivial(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List latentTypes, boolean expandFrames) { super(instrumentedType, instrumentedMethod, Collections.emptyList(), latentTypes, Collections.emptyList(), Collections.emptyList(), expandFrames); } /** * {@inheritDoc} */ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull Object[] stack) { methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack); } /** * {@inheritDoc} */ public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { throw new IllegalStateException("Did not expect exit advice " + adviceMethod + " for " + instrumentedMethod); } /** * {@inheritDoc} */ public void injectReturnFrame(MethodVisitor methodVisitor) { throw new IllegalStateException("Did not expect return frame for " + instrumentedMethod); } /** * {@inheritDoc} */ public void injectExceptionFrame(MethodVisitor methodVisitor) { throw new IllegalStateException("Did not expect exception frame for " + instrumentedMethod); } /** * {@inheritDoc} */ public void injectCompletionFrame(MethodVisitor methodVisitor) { throw new IllegalStateException("Did not expect completion frame for " + instrumentedMethod); } /** * {@inheritDoc} */ public void injectPostCompletionFrame(MethodVisitor methodVisitor) { throw new IllegalStateException("Did not expect post completion frame for " + instrumentedMethod); } /** * {@inheritDoc} */ public void injectInitializationFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void injectStartFrame(MethodVisitor methodVisitor) { /* do nothing */ } } /** * A stack map frame handler that requires the original arguments of the instrumented method to be preserved in their original form. */ protected abstract static class WithPreservedArguments extends Default { /** * {@code true} if a completion frame for the method bust be a full frame to reflect an initialization change. */ protected boolean allowCompactCompletionFrame; /** * Creates a new stack map frame handler that requires the stack map frames of the original arguments to be preserved. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. * @param allowCompactCompletionFrame {@code true} if a completion frame for the method bust be a full frame to reflect an initialization change. */ protected WithPreservedArguments(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, List latentTypes, List preMethodTypes, List postMethodTypes, boolean expandFrames, boolean allowCompactCompletionFrame) { super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames); this.allowCompactCompletionFrame = allowCompactCompletionFrame; } @Override @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE", justification = "ASM models frames by reference identity.") protected void translateFrame(MethodVisitor methodVisitor, TranslationMode translationMode, MethodDescription methodDescription, List additionalTypes, int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull Object[] stack) { if (type == Opcodes.F_FULL && localVariableLength > 0 && localVariable[0] != Opcodes.UNINITIALIZED_THIS) { allowCompactCompletionFrame = true; } super.translateFrame(methodVisitor, translationMode, methodDescription, additionalTypes, type, localVariableLength, localVariable, stackSize, stack); } /** * {@inheritDoc} */ public StackMapFrameHandler.ForAdvice bindExit(MethodDescription.InDefinedShape adviceMethod) { return new ForAdvice(adviceMethod, CompoundList.of(initialTypes, preMethodTypes, postMethodTypes), Collections.emptyList(), Collections.emptyList(), TranslationMode.EXIT, Initialization.INITIALIZED); } /** * {@inheritDoc} */ public void injectReturnFrame(MethodVisitor methodVisitor) { if (!expandFrames && currentFrameDivergence == 0) { 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[]{Initialization.INITIALIZED.toFrame(instrumentedMethod.getReturnType().asErasure())}); } } else { injectFullFrame(methodVisitor, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes), instrumentedMethod.getReturnType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(instrumentedMethod.getReturnType().asErasure())); } } /** * {@inheritDoc} */ 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, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes), Collections.singletonList(TypeDescription.ForLoadedType.of(Throwable.class))); } } /** * {@inheritDoc} */ public void injectCompletionFrame(MethodVisitor methodVisitor) { if (allowCompactCompletionFrame && !expandFrames && currentFrameDivergence == 0 && postMethodTypes.size() < 4) { if (postMethodTypes.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { Object[] local = new Object[postMethodTypes.size()]; int index = 0; for (TypeDescription typeDescription : postMethodTypes) { local[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY); } } else { injectFullFrame(methodVisitor, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes, postMethodTypes), Collections.emptyList()); } } /** * {@inheritDoc} */ public void injectPostCompletionFrame(MethodVisitor methodVisitor) { if (!expandFrames && currentFrameDivergence == 0) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { injectFullFrame(methodVisitor, Initialization.INITIALIZED, CompoundList.of(initialTypes, preMethodTypes, postMethodTypes), Collections.emptyList()); } } /** * {@inheritDoc} */ public void injectInitializationFrame(MethodVisitor methodVisitor) { if (!initialTypes.isEmpty()) { if (!expandFrames && initialTypes.size() < 4) { Object[] localVariable = new Object[initialTypes.size()]; int index = 0; for (TypeDescription typeDescription : initialTypes) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } methodVisitor.visitFrame(Opcodes.F_APPEND, localVariable.length, localVariable, EMPTY.length, EMPTY); } else { Object[] localVariable = new Object[(instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size() + initialTypes.size()]; int index = 0; if (instrumentedMethod.isConstructor()) { localVariable[index++] = Opcodes.UNINITIALIZED_THIS; } else if (!instrumentedMethod.isStatic()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } for (TypeDescription typeDescription : initialTypes) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } methodVisitor.visitFrame(expandFrames ? Opcodes.F_NEW : Opcodes.F_FULL, localVariable.length, localVariable, EMPTY.length, EMPTY); } } } /** * A stack map frame handler that expects that the original argument frames remain preserved throughout the original invocation. */ protected static class WithoutArgumentCopy extends WithPreservedArguments { /** * Creates a new stack map frame handler that expects the original frames to be preserved. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param latentTypes A list of virtual arguments that are available after the enter advice method is executed. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. * @param allowCompactCompletionFrame {@code true} if a completion frame for the method bust be a full frame to reflect an initialization change. */ protected WithoutArgumentCopy(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, List latentTypes, List preMethodTypes, List postMethodTypes, boolean expandFrames, boolean allowCompactCompletionFrame) { super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames, allowCompactCompletionFrame); } /** * {@inheritDoc} */ public void injectStartFrame(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull Object[] stack) { translateFrame(methodVisitor, TranslationMode.COPY, instrumentedMethod, CompoundList.of(initialTypes, preMethodTypes), type, localVariableLength, localVariable, stackSize, stack); } } /** * A stack map frame handler that expects that an argument copy of the original method arguments was made. */ protected static class WithArgumentCopy extends WithPreservedArguments { /** * Creates a new stack map frame handler that expects an argument copy. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param initialTypes A list of virtual method arguments that are explicitly added before any code execution. * @param latentTypes The types that are given post execution of a possible enter advice. * @param preMethodTypes A list of virtual method arguments that are available before the instrumented method is executed. * @param postMethodTypes A list of virtual method arguments that are available after the instrumented method has completed. * @param expandFrames {@code true} if the meta data handler is expected to expand its frames. */ protected WithArgumentCopy(TypeDescription instrumentedType, MethodDescription instrumentedMethod, List initialTypes, List latentTypes, List preMethodTypes, List postMethodTypes, boolean expandFrames) { super(instrumentedType, instrumentedMethod, initialTypes, latentTypes, preMethodTypes, postMethodTypes, expandFrames, true); } /** * {@inheritDoc} */ public void injectStartFrame(MethodVisitor methodVisitor) { if (!instrumentedMethod.isStatic() || !instrumentedMethod.getParameters().isEmpty()) { if (!expandFrames && (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size() < 4) { Object[] localVariable = new Object[(instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size()]; int index = 0; if (instrumentedMethod.isConstructor()) { localVariable[index++] = Opcodes.UNINITIALIZED_THIS; } else if (!instrumentedMethod.isStatic()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } methodVisitor.visitFrame(Opcodes.F_APPEND, localVariable.length, localVariable, EMPTY.length, EMPTY); } else { Object[] localVariable = new Object[(instrumentedMethod.isStatic() ? 0 : 2) + instrumentedMethod.getParameters().size() * 2 + initialTypes.size() + preMethodTypes.size()]; int index = 0; if (instrumentedMethod.isConstructor()) { localVariable[index++] = Opcodes.UNINITIALIZED_THIS; } else if (!instrumentedMethod.isStatic()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } for (TypeDescription typeDescription : initialTypes) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } for (TypeDescription typeDescription : preMethodTypes) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } if (instrumentedMethod.isConstructor()) { localVariable[index++] = Opcodes.UNINITIALIZED_THIS; } else if (!instrumentedMethod.isStatic()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { localVariable[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } methodVisitor.visitFrame(expandFrames ? Opcodes.F_NEW : Opcodes.F_FULL, localVariable.length, localVariable, EMPTY.length, EMPTY); } } currentFrameDivergence = (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size(); } /** * {@inheritDoc} */ @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE", justification = "ASM models frames by reference identity.") public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull 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: Object[] translated = new Object[localVariableLength + (instrumentedMethod.isStatic() ? 0 : 1) + instrumentedMethod.getParameters().size() + initialTypes.size() + preMethodTypes.size()]; int index = 0; if (instrumentedMethod.isConstructor()) { Initialization initialization = Initialization.INITIALIZED; for (int variableIndex = 0; variableIndex < localVariableLength; variableIndex++) { if (localVariable[variableIndex] == Opcodes.UNINITIALIZED_THIS) { initialization = Initialization.UNITIALIZED; break; } } translated[index++] = initialization.toFrame(instrumentedType); } else if (!instrumentedMethod.isStatic()) { translated[index++] = Initialization.INITIALIZED.toFrame(instrumentedType); } for (TypeDescription typeDescription : instrumentedMethod.getParameters().asTypeList().asErasures()) { translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } for (TypeDescription typeDescription : initialTypes) { translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } for (TypeDescription typeDescription : preMethodTypes) { translated[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } if (localVariableLength > 0) { System.arraycopy(localVariable, 0, translated, index, localVariableLength); } localVariableLength = translated.length; localVariable = translated; currentFrameDivergence = localVariableLength; break; default: throw new IllegalArgumentException("Unexpected frame type: " + type); } methodVisitor.visitFrame(type, localVariableLength, localVariable, stackSize, stack); } } } /** * 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; /** * The types provided before execution of the advice code. */ protected final List startTypes; /** * The types that are given post execution of the advice. */ private final List intermediateTypes; /** * The types provided after execution of the advice code. */ protected final List endTypes; /** * The translation mode to apply for this advice method. Should be either {@link TranslationMode#ENTER} or {@link TranslationMode#EXIT}. */ protected final TranslationMode translationMode; /** * The initialization to apply when resolving a reference to the instance on which a non-static method is invoked. */ private final Initialization initialization; /** * {@code true} if an intermediate frame was yielded. */ private boolean intermedate; /** * Creates a new meta data handler for an advice method. * * @param adviceMethod The method description for which frames are translated. * @param startTypes The types provided before execution of the advice code. * @param intermediateTypes The types that are given post execution of the advice. * @param endTypes The types provided after execution of the advice code. * @param translationMode The translation mode to apply for this advice method. Should be * either {@link TranslationMode#ENTER} or {@link TranslationMode#EXIT}. * @param initialization The initialization to apply when resolving a reference to the instance on which a non-static method is invoked. */ protected ForAdvice(MethodDescription.InDefinedShape adviceMethod, List startTypes, List intermediateTypes, List endTypes, TranslationMode translationMode, Initialization initialization) { this.adviceMethod = adviceMethod; this.startTypes = startTypes; this.intermediateTypes = intermediateTypes; this.endTypes = endTypes; this.translationMode = translationMode; this.initialization = initialization; intermedate = false; } /** * {@inheritDoc} */ public void translateFrame(MethodVisitor methodVisitor, int type, int localVariableLength, @MaybeNull Object[] localVariable, int stackSize, @MaybeNull Object[] stack) { Default.this.translateFrame(methodVisitor, translationMode, adviceMethod, startTypes, type, localVariableLength, localVariable, stackSize, stack); } /** * {@inheritDoc} */ public void injectReturnFrame(MethodVisitor methodVisitor) { if (!expandFrames && currentFrameDivergence == 0) { if (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[]{Initialization.INITIALIZED.toFrame(adviceMethod.getReturnType().asErasure())}); } } else { injectFullFrame(methodVisitor, initialization, startTypes, adviceMethod.getReturnType().represents(void.class) ? Collections.emptyList() : Collections.singletonList(adviceMethod.getReturnType().asErasure())); } } /** * {@inheritDoc} */ 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, initialization, startTypes, Collections.singletonList(TypeDescription.ForLoadedType.of(Throwable.class))); } } /** * {@inheritDoc} */ public void injectCompletionFrame(MethodVisitor methodVisitor) { if (expandFrames) { injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, endTypes), Collections.emptyList()); } else if (currentFrameDivergence == 0 && (intermedate || endTypes.size() < 4)) { if (intermedate || endTypes.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { Object[] local = new Object[endTypes.size()]; int index = 0; for (TypeDescription typeDescription : endTypes) { local[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY); } } else if (currentFrameDivergence < 3 && endTypes.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_CHOP, currentFrameDivergence, EMPTY, EMPTY.length, EMPTY); currentFrameDivergence = 0; } else { injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, endTypes), Collections.emptyList()); } } /** * {@inheritDoc} */ public void injectIntermediateFrame(MethodVisitor methodVisitor, List stack) { if (expandFrames) { injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, intermediateTypes), stack); } else if (intermedate && stack.size() < 2) { if (stack.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Initialization.INITIALIZED.toFrame(stack.get(0))}); } } else if (currentFrameDivergence == 0 && intermediateTypes.size() < 4 && (stack.isEmpty() || stack.size() < 2 && intermediateTypes.isEmpty())) { if (intermediateTypes.isEmpty()) { if (stack.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_SAME, EMPTY.length, EMPTY, EMPTY.length, EMPTY); } else { methodVisitor.visitFrame(Opcodes.F_SAME1, EMPTY.length, EMPTY, 1, new Object[]{Initialization.INITIALIZED.toFrame(stack.get(0))}); } } else { Object[] local = new Object[intermediateTypes.size()]; int index = 0; for (TypeDescription typeDescription : intermediateTypes) { local[index++] = Initialization.INITIALIZED.toFrame(typeDescription); } methodVisitor.visitFrame(Opcodes.F_APPEND, local.length, local, EMPTY.length, EMPTY); } } else if (currentFrameDivergence < 3 && intermediateTypes.isEmpty() && stack.isEmpty()) { methodVisitor.visitFrame(Opcodes.F_CHOP, currentFrameDivergence, EMPTY, EMPTY.length, EMPTY); } else { injectFullFrame(methodVisitor, initialization, CompoundList.of(startTypes, intermediateTypes), stack); } currentFrameDivergence = intermediateTypes.size() - endTypes.size(); intermedate = true; } } } } /** * An exception handler is responsible for providing byte code for handling an exception thrown from a suppressing advice method. */ public interface ExceptionHandler { /** * Resolves a stack manipulation to apply. * * @param instrumentedMethod The instrumented method. * @param instrumentedType The instrumented type. * @return The stack manipulation to use. */ StackManipulation resolve(MethodDescription instrumentedMethod, TypeDescription instrumentedType); /** * Default implementations for commonly used exception handlers. */ enum Default implements ExceptionHandler { /** * An exception handler the suppresses the exception. */ SUPPRESSING { /** {@inheritDoc} */ public StackManipulation resolve(MethodDescription instrumentedMethod, TypeDescription instrumentedType) { return Removal.SINGLE; } }, /** * An exception handler that invokes {@link Throwable#printStackTrace()}. */ PRINTING { /** {@inheritDoc} */ public StackManipulation resolve(MethodDescription instrumentedMethod, TypeDescription instrumentedType) { try { return MethodInvocation.invoke(new MethodDescription.ForLoadedMethod(Throwable.class.getMethod("printStackTrace"))); } catch (NoSuchMethodException exception) { throw new IllegalStateException("Cannot locate Throwable::printStackTrace"); } } }, /** * An exception handler that rethrows any suppressed {@link Throwable}. Normally, it is preferable to avoid suppression * altogether rather then to rethrow them. It can however be desired to make this behavior configurable where using this * handler allows to effectively disable the suppression. */ RETHROWING { /** {@inheritDoc} */ public StackManipulation resolve(MethodDescription instrumentedMethod, TypeDescription instrumentedType) { return Throw.INSTANCE; } } } /** * A simple exception handler that returns a fixed stack manipulation. */ @HashCodeAndEqualsPlugin.Enhance class Simple implements ExceptionHandler { /** * The stack manipulation to execute. */ private final StackManipulation stackManipulation; /** * Creates a new simple exception handler. * * @param stackManipulation The stack manipulation to execute. */ public Simple(StackManipulation stackManipulation) { this.stackManipulation = stackManipulation; } /** * {@inheritDoc} */ public StackManipulation resolve(MethodDescription instrumentedMethod, TypeDescription instrumentedType) { return stackManipulation; } } } /** * A dispatcher for implementing advice. */ protected interface Dispatcher { /** * Indicates that a method does not represent advice and does not need to be visited. */ @AlwaysNull MethodVisitor IGNORE_METHOD = null; /** * Expresses that an annotation should not be visited. */ @AlwaysNull AnnotationVisitor IGNORE_ANNOTATION = null; /** * Returns {@code true} if this dispatcher is alive. * * @return {@code true} if this dispatcher is alive. */ boolean isAlive(); /** * The type that is produced as a result of executing this advice method. * * @return A description of the type that is produced by this advice method. */ TypeDefinition getAdviceType(); /** * 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(); /** * Returns the named types declared by this enter advice. * * @return The named types declared by this enter advice. */ Map getNamedTypes(); /** * 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. * @param methodExit The unresolved dispatcher for the method exit advice. * @param postProcessorFactory The post processor factory to use. * @return This dispatcher as a dispatcher for entering a method. */ Resolved.ForMethodEnter asMethodEnter(List> userFactories, @MaybeNull ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory); /** * 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 methodEnter The unresolved dispatcher for the method enter advice. * @param postProcessorFactory The post processor factory to use. * @return This dispatcher as a dispatcher for exiting a method. */ Resolved.ForMethodExit asMethodExit(List> userFactories, @MaybeNull ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory); } /** * 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 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 returnType The return type of the advice method. */ void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType); /** * Invoked at the end of a method if the exception handler should be wrapped in a skipping block. * * @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 returnType The return type of the advice method. */ void onEndWithSkip(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType); } /** * A non-operational suppression handler that does not suppress any method. */ enum NoOp implements SuppressionHandler, Bound { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Bound bind(StackManipulation exceptionHandler) { return this; } /** * {@inheritDoc} */ public void onPrepare(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void onStart(MethodVisitor methodVisitor) { /* do nothing */ } /** * {@inheritDoc} */ public void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { /* do nothing */ } /** * {@inheritDoc} */ public void onEndWithSkip(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { /* do nothing */ } } /** * A suppression handler that suppresses a given throwable type. */ @HashCodeAndEqualsPlugin.Enhance 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); } /** * {@inheritDoc} */ 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(); } /** * {@inheritDoc} */ public void onPrepare(MethodVisitor methodVisitor) { methodVisitor.visitTryCatchBlock(startOfMethod, endOfMethod, endOfMethod, suppressedType.getInternalName()); } /** * {@inheritDoc} */ public void onStart(MethodVisitor methodVisitor) { methodVisitor.visitLabel(startOfMethod); } /** * {@inheritDoc} */ public void onEnd(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { methodVisitor.visitLabel(endOfMethod); stackMapFrameHandler.injectExceptionFrame(methodVisitor); methodSizeHandler.requireStackSize(1 + exceptionHandler.apply(methodVisitor, implementationContext).getMaximalSize()); if (returnType.represents(boolean.class) || returnType.represents(byte.class) || returnType.represents(short.class) || returnType.represents(char.class) || returnType.represents(int.class)) { methodVisitor.visitInsn(Opcodes.ICONST_0); } else if (returnType.represents(long.class)) { methodVisitor.visitInsn(Opcodes.LCONST_0); } else if (returnType.represents(float.class)) { methodVisitor.visitInsn(Opcodes.FCONST_0); } else if (returnType.represents(double.class)) { methodVisitor.visitInsn(Opcodes.DCONST_0); } else if (!returnType.represents(void.class)) { methodVisitor.visitInsn(Opcodes.ACONST_NULL); } } /** * {@inheritDoc} */ public void onEndWithSkip(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodSizeHandler.ForAdvice methodSizeHandler, StackMapFrameHandler.ForAdvice stackMapFrameHandler, TypeDefinition returnType) { Label skipExceptionHandler = new Label(); methodVisitor.visitJumpInsn(Opcodes.GOTO, skipExceptionHandler); onEnd(methodVisitor, implementationContext, methodSizeHandler, stackMapFrameHandler, returnType); methodVisitor.visitLabel(skipExceptionHandler); stackMapFrameHandler.injectReturnFrame(methodVisitor); } } } } /** * A relocation handler is responsible for chaining the usual control flow of an instrumented method. */ interface RelocationHandler { /** * Binds this relocation handler to a relocation dispatcher. * * @param instrumentedMethod The instrumented method. * @param relocation The relocation to apply. * @return A bound relocation handler. */ Bound bind(MethodDescription instrumentedMethod, Relocation relocation); /** * A relocator is responsible for triggering a relocation if a relocation handler triggers a relocating condition. */ interface Relocation { /** * Applies this relocator. * * @param methodVisitor The method visitor to use. */ void apply(MethodVisitor methodVisitor); /** * A relocation that unconditionally jumps to a given label. */ @HashCodeAndEqualsPlugin.Enhance class ForLabel implements Relocation { /** * The label to jump to. */ private final Label label; /** * Creates a new relocation for an unconditional jump to a given label. * * @param label The label to jump to. */ public ForLabel(Label label) { this.label = label; } /** * {@inheritDoc} */ public void apply(MethodVisitor methodVisitor) { methodVisitor.visitJumpInsn(Opcodes.GOTO, label); } } } /** * A bound {@link RelocationHandler}. */ interface Bound { /** * Indicates that this relocation handler does not require a minimal stack size. */ int NO_REQUIRED_SIZE = 0; /** * Applies this relocation handler. * * @param methodVisitor The method visitor to use. * @param implementationContext The implementation context to use. * @param offset The offset of the relevant value. * @return The minimal required stack size to apply this relocation handler. */ int apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, int offset); } /** * A disabled relocation handler that does never trigger a relocation. */ enum Disabled implements RelocationHandler, Bound { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return this; } /** * {@inheritDoc} */ public int apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, int offset) { return NO_REQUIRED_SIZE; } } /** * A relocation handler factory that triggers a relocation for a default or non-default value. */ enum ForValue { /** * A relocation handler for an {@code int} type or any compatible type. */ BOOLEAN(Opcodes.ILOAD, Opcodes.BALOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }, /** * A relocation handler for an {@code int} type or any compatible type. */ BYTE(Opcodes.ILOAD, Opcodes.BALOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }, /** * A relocation handler for an {@code short} type or any compatible type. */ SHORT(Opcodes.ILOAD, Opcodes.SALOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }, /** * A relocation handler for an {@code char} type or any compatible type. */ CHARACTER(Opcodes.ILOAD, Opcodes.CALOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }, /** * A relocation handler for an {@code int} type or any compatible type. */ INTEGER(Opcodes.ILOAD, Opcodes.IALOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }, /** * A relocation handler for a {@code long} type. */ LONG(Opcodes.LLOAD, Opcodes.LALOAD, Opcodes.IFNE, Opcodes.IFEQ, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { methodVisitor.visitInsn(Opcodes.L2I); } }, /** * A relocation handler for a {@code float} type. */ FLOAT(Opcodes.FLOAD, Opcodes.FALOAD, Opcodes.IFNE, Opcodes.IFEQ, 2) { @Override protected void convertValue(MethodVisitor methodVisitor) { methodVisitor.visitInsn(Opcodes.FCONST_0); methodVisitor.visitInsn(Opcodes.FCMPL); } }, /** * A relocation handler for a {@code double} type. */ DOUBLE(Opcodes.DLOAD, Opcodes.DALOAD, Opcodes.IFNE, Opcodes.IFEQ, 4) { @Override protected void convertValue(MethodVisitor methodVisitor) { methodVisitor.visitInsn(Opcodes.DCONST_0); methodVisitor.visitInsn(Opcodes.DCMPL); } }, /** * A relocation handler for a reference type. */ REFERENCE(Opcodes.ALOAD, Opcodes.AALOAD, Opcodes.IFNONNULL, Opcodes.IFNULL, 0) { @Override protected void convertValue(MethodVisitor methodVisitor) { /* do nothing */ } }; /** * An opcode for loading a value of the represented type from the local variable array. */ private final int load; /** * An opcode for loading a value of the represented type from an array. */ private final int arrayLoad; /** * The opcode to check for a non-default value. */ private final int defaultJump; /** * The opcode to check for a default value. */ private final int nonDefaultJump; /** * The minimal required stack size to apply this relocation handler. */ private final int requiredSize; /** * Creates a new relocation handler for a type's default or non-default value. * * @param load An opcode for loading a value of the represented type from the local variable array. * @param arrayLoad An opcode for loading a value of the represented type from an array. * @param defaultJump The opcode to check for a non-default value. * @param nonDefaultJump The opcode to check for a default value. * @param requiredSize The minimal required stack size to apply this relocation handler. */ ForValue(int load, int arrayLoad, int defaultJump, int nonDefaultJump, int requiredSize) { this.load = load; this.arrayLoad = arrayLoad; this.defaultJump = defaultJump; this.nonDefaultJump = nonDefaultJump; this.requiredSize = requiredSize; } /** * Resolves a relocation handler for a given type. * * @param typeDefinition The type to be resolved for a relocation attempt. * @param index The index in the array returned by the advice method that contains the value to be checked. * @param inverted {@code true} if the relocation should be applied for any non-default value of a type. * @return An appropriate relocation handler. */ protected static RelocationHandler of(TypeDefinition typeDefinition, int index, boolean inverted) { ForValue skipDispatcher; if (typeDefinition.represents(boolean.class)) { skipDispatcher = BOOLEAN; } else if (typeDefinition.represents(byte.class)) { skipDispatcher = BYTE; } else if (typeDefinition.represents(short.class)) { skipDispatcher = SHORT; } else if (typeDefinition.represents(char.class)) { skipDispatcher = CHARACTER; } else if (typeDefinition.represents(int.class)) { skipDispatcher = INTEGER; } else if (typeDefinition.represents(long.class)) { skipDispatcher = LONG; } else if (typeDefinition.represents(float.class)) { skipDispatcher = FLOAT; } else if (typeDefinition.represents(double.class)) { skipDispatcher = DOUBLE; } else if (typeDefinition.represents(void.class)) { throw new IllegalStateException("Cannot skip on default value for void return type"); } else { skipDispatcher = REFERENCE; } return inverted ? skipDispatcher.new OfNonDefault(index) : skipDispatcher.new OfDefault(index); } /** * Applies a value conversion prior to a applying a conditional jump. * * @param methodVisitor The method visitor to use. */ protected abstract void convertValue(MethodVisitor methodVisitor); /** * A relocation handler that checks for a value being a default value. */ @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) protected class OfDefault implements RelocationHandler { /** * The index of the array returned by the advice method that contains the value to check for its value. */ private final int index; /** * A relocation handler that checks if a value is a default value. * * @param index The index of the array returned by the advice method that contains the value to check for its value. */ public OfDefault(int index) { this.index = index; } /** * {@inheritDoc} */ public Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return new ForValue.Bound(instrumentedMethod, relocation, index, false); } } /** * A relocation handler that checks for a value being a non-default value. */ @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) protected class OfNonDefault implements RelocationHandler { /** * The index of the array returned by the advice method that contains the value to check for its value. */ private final int index; /** * A relocation handler that checks if a value is a non-default value. * * @param index The index of the array returned by the advice method that contains the value to check for its value. */ protected OfNonDefault(int index) { this.index = index; } /** * {@inheritDoc} */ public Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return new ForValue.Bound(instrumentedMethod, relocation, index, true); } } /** * A bound relocation handler for {@link ForValue}. */ @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) protected class Bound implements RelocationHandler.Bound { /** * The instrumented method. */ private final MethodDescription instrumentedMethod; /** * The relocation to apply. */ private final Relocation relocation; /** * The array index of the relocated value. */ private final int index; /** * {@code true} if the relocation should be applied for any non-default value of a type. */ private final boolean inverted; /** * Creates a new bound relocation handler. * * @param instrumentedMethod The instrumented method. * @param relocation The relocation to apply. * @param index The array index of the relocated value. * @param inverted {@code true} if the relocation should be applied for any non-default value of a type. */ protected Bound(MethodDescription instrumentedMethod, Relocation relocation, int index, boolean inverted) { this.instrumentedMethod = instrumentedMethod; this.relocation = relocation; this.index = index; this.inverted = inverted; } /** * {@inheritDoc} */ public int apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, int offset) { if (instrumentedMethod.isConstructor()) { throw new IllegalStateException("Cannot skip code execution from constructor: " + instrumentedMethod); } Label noSkip = new Label(); int size; if (index < 0) { size = requiredSize; methodVisitor.visitVarInsn(load, offset); } else { methodVisitor.visitVarInsn(Opcodes.ALOAD, offset); methodVisitor.visitJumpInsn(Opcodes.IFNULL, noSkip); methodVisitor.visitVarInsn(Opcodes.ALOAD, offset); size = Math.max(requiredSize, IntegerConstant.forValue(index) .apply(methodVisitor, implementationContext) .getMaximalSize() + 1); methodVisitor.visitInsn(arrayLoad); } convertValue(methodVisitor); methodVisitor.visitJumpInsn(inverted ? nonDefaultJump : defaultJump, noSkip); relocation.apply(methodVisitor); methodVisitor.visitLabel(noSkip); return size; } } } /** * A relocation handler that is triggered if the checked value is an instance of a given type. */ @HashCodeAndEqualsPlugin.Enhance class ForType implements RelocationHandler { /** * The type that triggers a relocation. */ private final TypeDescription typeDescription; /** * The index of the array returned by the advice method that contains the value to check for its type. */ private final int index; /** * Creates a new relocation handler that triggers a relocation if a value is an instance of a given type. * * @param typeDescription The type that triggers a relocation. * @param index The index of the array returned by the advice method that contains the value to check for its type. */ protected ForType(TypeDescription typeDescription, int index) { this.typeDescription = typeDescription; this.index = index; } /** * Resolves a relocation handler that is triggered if the checked instance is of a given type. * * @param typeDescription The type that triggers a relocation. * @param index The array index of the value that is returned. * @param returnedType The type that is returned by the advice method. * @return An appropriate relocation handler. */ @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming component type for array type.") protected static RelocationHandler of(TypeDescription typeDescription, int index, TypeDefinition returnedType) { TypeDefinition targetType; if (index < 0) { targetType = returnedType; } else if (returnedType.isArray()) { targetType = returnedType.getComponentType(); } else { throw new IllegalStateException(returnedType + " is not an array type but an index for a relocation is defined"); } if (typeDescription.represents(void.class)) { return Disabled.INSTANCE; } else if (typeDescription.represents(OnDefaultValue.class)) { return ForValue.of(targetType, index, false); } else if (typeDescription.represents(OnNonDefaultValue.class)) { return ForValue.of(targetType, index, true); } else if (typeDescription.isPrimitive() || targetType.isPrimitive()) { throw new IllegalStateException("Cannot relocate execution by instance type for primitive type"); } else { return new ForType(typeDescription, index); } } /** * {@inheritDoc} */ public RelocationHandler.Bound bind(MethodDescription instrumentedMethod, Relocation relocation) { return new Bound(instrumentedMethod, relocation); } /** * A bound relocation handler for {@link ForType}. */ @HashCodeAndEqualsPlugin.Enhance(includeSyntheticFields = true) protected class Bound implements RelocationHandler.Bound { /** * The instrumented method. */ private final MethodDescription instrumentedMethod; /** * The relocation to use. */ private final Relocation relocation; /** * Creates a new bound relocation handler. * * @param instrumentedMethod The instrumented method. * @param relocation The relocation to apply. */ protected Bound(MethodDescription instrumentedMethod, Relocation relocation) { this.instrumentedMethod = instrumentedMethod; this.relocation = relocation; } /** * {@inheritDoc} */ public int apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, int offset) { if (instrumentedMethod.isConstructor()) { throw new IllegalStateException("Cannot skip code execution from constructor: " + instrumentedMethod); } methodVisitor.visitVarInsn(Opcodes.ALOAD, offset); Label noSkip = new Label(); int size; if (index < 0) { size = NO_REQUIRED_SIZE; } else { methodVisitor.visitJumpInsn(Opcodes.IFNULL, noSkip); methodVisitor.visitVarInsn(Opcodes.ALOAD, offset); size = IntegerConstant.forValue(index) .apply(methodVisitor, implementationContext) .getMaximalSize() + 1; methodVisitor.visitInsn(Opcodes.AALOAD); } methodVisitor.visitTypeInsn(Opcodes.INSTANCEOF, typeDescription.getInternalName()); methodVisitor.visitJumpInsn(Opcodes.IFEQ, noSkip); relocation.apply(methodVisitor); methodVisitor.visitLabel(noSkip); return size; } } } } /** * Represents a resolved dispatcher. */ interface Resolved extends Dispatcher { /** * Returns the named types defined by this advice. * * @return The named types defined by this advice. */ Map getNamedTypes(); /** * 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 argumentHandler A handler for accessing values on the local variable array. * @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. * @param relocation A relocation to use with a relocation handler. * @return A dispatcher that is bound to the instrumented method. */ Bound bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, ArgumentHandler.ForInstrumentedMethod argumentHandler, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler, RelocationHandler.Relocation relocation); /** * Represents a resolved dispatcher for entering a method. */ interface ForMethodEnter extends Resolved { /** * 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(); /** * Returns the actual advice type, even if it is not required post advice processing. * * @return The actual advice type, even if it is not required post advice processing. */ TypeDefinition getActualAdviceType(); } /** * 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(); /** * Returns a factory for creating an {@link ArgumentHandler}. * * @return A factory for creating an {@link ArgumentHandler}. */ ArgumentHandler.Factory getArgumentHandlerFactory(); } /** * An abstract base implementation of a {@link Resolved} dispatcher. */ @HashCodeAndEqualsPlugin.Enhance abstract class AbstractBase implements Resolved { /** * The represented advice method. */ protected final MethodDescription.InDefinedShape adviceMethod; /** * The post processor to apply. */ protected final PostProcessor postProcessor; /** * A mapping from offset to a mapping for this offset with retained iteration order of the method's parameters. */ protected final Map offsetMappings; /** * The suppression handler to use. */ protected final SuppressionHandler suppressionHandler; /** * The relocation handler to use. */ protected final RelocationHandler relocationHandler; /** * Creates a new resolved version of a dispatcher. * * @param adviceMethod The represented advice method. * @param postProcessor The post processor to use. * @param factories A list of factories to resolve for the parameters of the advice method. * @param throwableType The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions. * @param relocatableType The type to trigger a relocation of the method's control flow or {@code void} if no relocation should be executed. * @param relocatableIndex The index within an array that is returned by the advice method, indicating the value to consider for relocation. * @param adviceType The applied advice type. */ protected AbstractBase(MethodDescription.InDefinedShape adviceMethod, PostProcessor postProcessor, List> factories, TypeDescription throwableType, TypeDescription relocatableType, int relocatableIndex, OffsetMapping.Factory.AdviceType adviceType) { this.adviceMethod = adviceMethod; this.postProcessor = postProcessor; Map> offsetMappings = new HashMap>(); for (OffsetMapping.Factory factory : factories) { offsetMappings.put(TypeDescription.ForLoadedType.of(factory.getAnnotationType()), factory); } this.offsetMappings = new LinkedHashMap(); 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()), adviceType); 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); } suppressionHandler = SuppressionHandler.Suppressing.of(throwableType); relocationHandler = RelocationHandler.ForType.of(relocatableType, relocatableIndex, adviceMethod.getReturnType()); } /** * {@inheritDoc} */ public boolean isAlive() { return true; } } } /** * A bound resolution of an advice method. */ interface Bound { /** * Prepares the advice method's exception handlers. */ void prepare(); /** * Initialized the advice's methods local variables. */ void initialize(); /** * 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 { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public boolean isAlive() { return false; } /** * {@inheritDoc} */ public boolean isBinary() { return false; } /** * {@inheritDoc} */ public TypeDescription getAdviceType() { return TypeDescription.ForLoadedType.of(void.class); } /** * {@inheritDoc} */ public boolean isPrependLineNumber() { return false; } /** * {@inheritDoc} */ public TypeDefinition getActualAdviceType() { return TypeDescription.ForLoadedType.of(void.class); } /** * {@inheritDoc} */ public Map getNamedTypes() { return Collections.emptyMap(); } /** * {@inheritDoc} */ public TypeDescription getThrowable() { return NoExceptionHandler.DESCRIPTION; } /** * {@inheritDoc} */ public ArgumentHandler.Factory getArgumentHandlerFactory() { return ArgumentHandler.Factory.SIMPLE; } /** * {@inheritDoc} */ public Resolved.ForMethodEnter asMethodEnter(List> userFactories, @MaybeNull ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory) { return this; } /** * {@inheritDoc} */ public Resolved.ForMethodExit asMethodExit(List> userFactories, @MaybeNull ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory) { return this; } /** * {@inheritDoc} */ public void prepare() { /* do nothing */ } /** * {@inheritDoc} */ public void initialize() { /* do nothing */ } /** * {@inheritDoc} */ public void apply() { /* do nothing */ } /** * {@inheritDoc} */ public Bound bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, ArgumentHandler.ForInstrumentedMethod argumentHandler, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, StackManipulation exceptionHandler, RelocationHandler.Relocation relocation) { return this; } } /** * A dispatcher for an advice method that is being inlined into the instrumented method. */ @HashCodeAndEqualsPlugin.Enhance class Inlining implements Unresolved { /** * The advice method. */ protected final MethodDescription.InDefinedShape adviceMethod; /** * A mapping of all available local variables by their name to their type. */ private final Map namedTypes; /** * Creates a dispatcher for inlined advice method. * * @param adviceMethod The advice method. */ protected Inlining(MethodDescription.InDefinedShape adviceMethod) { this.adviceMethod = adviceMethod; namedTypes = new HashMap(); for (ParameterDescription parameterDescription : adviceMethod.getParameters()) { AnnotationDescription.Loadable annotationDescription = parameterDescription.getDeclaredAnnotations().ofType(Local.class); if (annotationDescription != null) { String name = annotationDescription.getValue(OffsetMapping.ForLocalValue.Factory.LOCAL_VALUE).resolve(String.class); TypeDefinition previous = namedTypes.put(name, parameterDescription.getType()); if (previous != null && !previous.equals(parameterDescription.getType())) { throw new IllegalStateException("Local variable for " + name + " is defined with inconsistent types"); } } } } /** * {@inheritDoc} */ public boolean isAlive() { return true; } /** * {@inheritDoc} */ public boolean isBinary() { return true; } /** * {@inheritDoc} */ public TypeDescription getAdviceType() { return adviceMethod.getReturnType().asErasure(); } /** * {@inheritDoc} */ public Map getNamedTypes() { return namedTypes; } /** * {@inheritDoc} */ public Dispatcher.Resolved.ForMethodEnter asMethodEnter(List> userFactories, @MaybeNull ClassReader classReader, Unresolved methodExit, PostProcessor.Factory postProcessorFactory) { if (classReader == null) { throw new IllegalStateException("Class reader not expected null"); } return Resolved.ForMethodEnter.of(adviceMethod, postProcessorFactory.make(adviceMethod, false), namedTypes, userFactories, methodExit.getAdviceType(), classReader, methodExit.isAlive()); } /** * {@inheritDoc} */ public Dispatcher.Resolved.ForMethodExit asMethodExit(List> userFactories, @MaybeNull ClassReader classReader, Unresolved methodEnter, PostProcessor.Factory postProcessorFactory) { Map namedTypes = new HashMap(methodEnter.getNamedTypes()), uninitializedNamedTypes = new HashMap(); for (Map.Entry entry : this.namedTypes.entrySet()) { TypeDefinition typeDefinition = namedTypes.get(entry.getKey()), uninitializedTypeDefinition = uninitializedNamedTypes.get(entry.getKey()); if (typeDefinition == null && uninitializedTypeDefinition == null) { namedTypes.put(entry.getKey(), entry.getValue()); uninitializedNamedTypes.put(entry.getKey(), entry.getValue()); } else if (!(typeDefinition == null ? uninitializedTypeDefinition : typeDefinition).equals(entry.getValue())) { throw new IllegalStateException("Local variable for " + entry.getKey() + " is defined with inconsistent types"); } } return Resolved.ForMethodExit.of(adviceMethod, postProcessorFactory.make(adviceMethod, true), namedTypes, uninitializedNamedTypes, userFactories, classReader, methodEnter.getAdviceType()); } @Override public String toString() { return "Delegate to " + adviceMethod; } /** * A resolved version of a dispatcher. */ protected abstract static class Resolved extends Dispatcher.Resolved.AbstractBase { /** * A class reader to query for the class file of the advice method. */ protected final ClassReader classReader; /** * Creates a new resolved version of a dispatcher. * * @param adviceMethod The represented advice method. * @param postProcessor The post processor to apply. * @param factories A list of factories to resolve for the parameters of the advice method. * @param throwableType The type to handle by a suppression handler or {@link NoExceptionHandler} to not handle any exceptions. * @param relocatableType The type to trigger a relocation of the method's control flow or {@code void} if no relocation should be executed. * @param relocatableIndex The index within an array that is returned by the advice method, indicating the value to consider for relocation. * @param classReader A class reader to query for the class file of the advice method. */ protected Resolved(MethodDescription.InDefinedShape adviceMethod, PostProcessor postProcessor, List> factories, TypeDescription throwableType, TypeDescription relocatableType, int relocatableIndex, ClassReader classReader) { super(adviceMethod, postProcessor, factories, throwableType, relocatableType, relocatableIndex, OffsetMapping.Factory.AdviceType.INLINING); this.classReader = classReader; } /** * Resolves the initialization types of this advice method. * * @param argumentHandler The argument handler to use for resolving the initialization. * @return A mapping of parameter offsets to the type to initialize. */ protected abstract Map resolveInitializationTypes(ArgumentHandler argumentHandler); /** * 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 argumentHandler A handler for accessing values on the local variable array. * @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 A bound suppression handler that is used for suppressing exceptions of this advice method. * @param relocationHandler A bound relocation handler that is responsible for considering a non-standard control flow. * @param exceptionHandler The exception handler that is resolved for the instrumented method. * @return A method visitor for visiting the advice method's byte code. */ protected abstract MethodVisitor apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, Assigner assigner, ArgumentHandler.ForInstrumentedMethod argumentHandler, MethodSizeHandler.ForInstrumentedMethod methodSizeHandler, StackMapFrameHandler.ForInstrumentedMethod stackMapFrameHandler, TypeDescription instrumentedType, MethodDescription instrumentedMethod, SuppressionHandler.Bound suppressionHandler, RelocationHandler.Bound relocationHandler, StackManipulation exceptionHandler); /** * 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 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 accessing values on the local variable array. */ protected final ArgumentHandler.ForInstrumentedMethod argumentHandler; /** * 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 bound relocation handler that is responsible for considering a non-standard control flow. */ protected final RelocationHandler.Bound relocationHandler; /** * The exception handler that is resolved for the instrumented method. */ protected final StackManipulation exceptionHandler; /** * 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 final List