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

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

Go to download

Byte Buddy is a Java library for creating Java classes at run time. This artifact is a build of Byte Buddy with a remaining dependency onto ASM. You should never depend on this module without repackaging Byte Buddy and ASM into your own namespace.

There is a newer version: 1.15.11
Show newest version
package net.bytebuddy.asm;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import net.bytebuddy.description.annotation.AnnotationDescription;
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.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.matcher.ElementMatcher;
import org.objectweb.asm.*;

import java.io.IOException;
import java.lang.annotation.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

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 the advice methods. 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. *

*

* Important: Currently, it is required to make ASM recompute stack sizes and stack map frames by setting the {@link ClassWriter#COMPUTE_FRAMES} * flag. This constraint will be relaxed in a future version. *

*/ public class Advice implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { /** * 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 binary representation of the class containing the advice methods. */ private final byte[] binaryRepresentation; /** * 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 binaryRepresentation The binary representation of the class containing the advice methods. */ protected Advice(Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, byte[] binaryRepresentation) { this.methodEnter = methodEnter; this.methodExit = methodExit; this.binaryRepresentation = binaryRepresentation; } /** * Implements advice where every matched method is advised by the given type's advisory methods. The advises binary representation is * accessed by querying the class loader of the supplied class for a class file. * * @param type The type declaring the advice. * @return A method visitor wrapper representing the supplied advice. */ public static Advice to(Class type) { return to(type, ClassFileLocator.ForClassLoader.of(type.getClassLoader())); } /** * Implements advice where every matched method is advised by the given type's advisory methods. * * @param type 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 type, ClassFileLocator classFileLocator) { return to(new TypeDescription.ForLoadedType(type), classFileLocator); } /** * Implements advice where every matched method is advised by the given type's advisory methods. * * @param typeDescription 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 typeDescription, ClassFileLocator classFileLocator) { try { Dispatcher methodEnter = Dispatcher.Inactive.INSTANCE, methodExit = Dispatcher.Inactive.INSTANCE; for (MethodDescription.InDefinedShape methodDescription : typeDescription.getDeclaredMethods()) { methodEnter = resolve(OnMethodEnter.class, methodEnter, methodDescription); methodExit = resolve(OnMethodExit.class, methodExit, methodDescription); } if (!methodEnter.isAlive() && !methodExit.isAlive()) { throw new IllegalArgumentException("No advice defined by " + typeDescription); } Dispatcher.Resolved.ForMethodEnter resolved = methodEnter.asMethodEnter(); return new Advice(methodEnter.asMethodEnter(), methodExit.asMethodExitTo(resolved), classFileLocator.locate(typeDescription.getName()).resolve()); } catch (IOException exception) { throw new IllegalStateException("Error reading class file of " + typeDescription, exception); } } /** * Checks if a given method represents advise and does some basic validation. * * @param annotation The annotation that indicates a given type of advise. * @param dispatcher Any previous dispatcher. * @param methodDescription A description of the method considered as advise. * @return A dispatcher for the given method or the supplied dispatcher if the given method is not intended to be used as advise. */ private static Dispatcher resolve(Class annotation, Dispatcher dispatcher, MethodDescription.InDefinedShape methodDescription) { if (methodDescription.getDeclaredAnnotations().isAnnotationPresent(annotation)) { 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"); } return new Dispatcher.Active(methodDescription); } else { return dispatcher; } } /** * 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().writerFlags(ClassWriter.COMPUTE_FRAMES).method(matcher, this); } @Override public MethodVisitor wrap(TypeDescription instrumentedType, MethodDescription.InDefinedShape methodDescription, MethodVisitor methodVisitor) { if (methodDescription.isAbstract() || methodDescription.isNative()) { throw new IllegalStateException("Cannot advice abstract or native method " + methodDescription); } return methodExit.isSkipThrowable() ? new AdviceVisitor.WithoutExceptionHandling(methodVisitor, methodDescription, methodEnter, methodExit, binaryRepresentation) : new AdviceVisitor.WithExceptionHandling(methodVisitor, methodDescription, methodEnter, methodExit, binaryRepresentation); } @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; Advice advice = (Advice) other; return methodEnter.equals(advice.methodEnter) && methodExit.equals(advice.methodExit) && Arrays.equals(binaryRepresentation, advice.binaryRepresentation); } @Override public int hashCode() { int result = methodEnter.hashCode(); result = 31 * result + methodExit.hashCode(); result = 31 * result + Arrays.hashCode(binaryRepresentation); return result; } @Override public String toString() { return "Advice{" + "methodEnter=" + methodEnter + ", methodExit=" + methodExit + ", binaryRepresentation=<" + binaryRepresentation.length + " bytes>" + '}'; } /** * A method visitor that weaves the advise methods' byte codes. */ protected abstract static class AdviceVisitor extends MethodVisitor { /** * Indicates a zero offset. */ private static final int NO_OFFSET = 0; /** * A description of the instrumented method. */ protected final MethodDescription.InDefinedShape instrumentedMethod; /** * The dispatcher to be used for method entry. */ private final Dispatcher.Resolved.ForMethodEnter methodEnter; /** * The dispatcher to be used for method exit. */ private final Dispatcher.Resolved.ForMethodExit methodExit; /** * A reader for traversing the advise methods' class file. */ private final ClassReader classReader; /** * Creates an advise visitor. * * @param methodVisitor The method visitor for the instrumented method. * @param instrumentedMethod A description of the instrumented method. * @param methodEnter The dispatcher to be used for method entry. * @param methodExit The dispatcher to be used for method exit. * @param binaryRepresentation The binary representation of the advise methods' class file. */ protected AdviceVisitor(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, byte[] binaryRepresentation) { super(Opcodes.ASM5, methodVisitor); this.instrumentedMethod = instrumentedMethod; this.methodEnter = methodEnter; this.methodExit = methodExit; this.classReader = new ClassReader(binaryRepresentation); } @Override public void visitCode() { super.visitCode(); onMethodStart(); } /** * Writes the advise for entering the instrumented method. */ protected abstract void onMethodStart(); @Override public void visitVarInsn(int opcode, int offset) { super.visitVarInsn(opcode, offset < instrumentedMethod.getStackSize() ? offset : offset + methodEnter.getEnterType().getStackSize().getSize()); } @Override @SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "Switch is supposed to fall through") public void visitInsn(int opcode) { switch (opcode) { case Opcodes.RETURN: onMethodExit(); break; case Opcodes.IRETURN: onMethodExit(Opcodes.ISTORE, Opcodes.ILOAD); break; case Opcodes.FRETURN: onMethodExit(Opcodes.FSTORE, Opcodes.FLOAD); break; case Opcodes.DRETURN: onMethodExit(Opcodes.DSTORE, Opcodes.DLOAD); break; case Opcodes.LRETURN: onMethodExit(Opcodes.LSTORE, Opcodes.LLOAD); break; case Opcodes.ARETURN: onMethodExit(Opcodes.ASTORE, Opcodes.ALOAD); break; } mv.visitInsn(opcode); } /** * Writes the advise for the instrumented method's end. * * @param store The return type's store instruction. * @param load The return type's load instruction. */ private void onMethodExit(int store, int load) { variable(store); mv.visitInsn(Opcodes.ACONST_NULL); variable(Opcodes.ASTORE, instrumentedMethod.getReturnType().getStackSize().getSize()); onMethodExit(); variable(load); } /** * Writes the advise for exiting the instrumented method. */ protected abstract void onMethodExit(); /** * Access the first variable after the instrumented variables and return type are stored. * * @param opcode The opcode for accessing the variable. */ protected void variable(int opcode) { variable(opcode, NO_OFFSET); } /** * Access the first variable after the instrumented variables and return type are stored. * * @param opcode The opcode for accessing the variable. * @param offset The additional offset of the variable. */ protected void variable(int opcode, int offset) { mv.visitVarInsn(opcode, instrumentedMethod.getStackSize() + methodEnter.getEnterType().getStackSize().getSize() + offset); } @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { /* do nothing */ } @Override public void visitMaxs(int maxStack, int maxLocals) { onMethodEnd(); super.visitMaxs(maxStack, maxLocals); } /** * Writes the advise for completing the instrumented method. */ protected abstract void onMethodEnd(); /** * Appends the enter advise's byte code. */ protected void appendEnter() { append(methodEnter); } /** * Appends the exit advise's byte code. */ protected void appendExit() { append(methodExit); } /** * Appends the byte code of the supplied dispatcher. * * @param dispatcher The dispatcher for which the byte code should be appended. */ private void append(Dispatcher.Resolved dispatcher) { classReader.accept(new CodeCopier(dispatcher), ClassReader.SKIP_DEBUG); } /** * A visitor for copying an advise method's byte code. */ protected class CodeCopier extends ClassVisitor { /** * The dispatcher to use. */ private final Dispatcher.Resolved dispatcher; /** * Creates a new code copier. * * @param dispatcher The dispatcher to use. */ protected CodeCopier(Dispatcher.Resolved dispatcher) { super(Opcodes.ASM5); this.dispatcher = dispatcher; } @Override public MethodVisitor visitMethod(int modifiers, String internalName, String descriptor, String signature, String[] exception) { return dispatcher.apply(internalName, descriptor, mv, instrumentedMethod); } @Override public String toString() { return "Advice.AdviceVisitor.CodeCopier{" + "outer=" + AdviceVisitor.this + ", dispatcher=" + dispatcher + '}'; } } /** * An advise visitor that captures exceptions by weaving try-catch blocks around user code. */ protected static class WithExceptionHandling extends AdviceVisitor { /** * Indicates that any throwable should be captured. */ private static final String ANY_THROWABLE = null; /** * The label indicating the start of the exception handler. */ private final Label handler; /** * The current label that indicates the next section that terminates user code. */ private Label userEnd; /** * Creates a new advise visitor that captures exception by weaving try-catch blocks around user code. * * @param methodVisitor The method visitor for the instrumented method. * @param instrumentedMethod A description of the instrumented method. * @param methodEnter The dispatcher to be used for method entry. * @param methodExit The dispatcher to be used for method exit. * @param binaryRepresentation The binary representation of the advise methods' class file. */ protected WithExceptionHandling(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, byte[] binaryRepresentation) { super(methodVisitor, instrumentedMethod, methodEnter, methodExit, binaryRepresentation); handler = new Label(); } @Override protected void onMethodStart() { appendEnter(); Label userStart = new Label(); userEnd = new Label(); mv.visitTryCatchBlock(userStart, userEnd, handler, null); mv.visitLabel(userStart); } @Override protected void onMethodExit() { mv.visitLabel(userEnd); appendExit(); Label userStart = new Label(); userEnd = new Label(); mv.visitTryCatchBlock(userStart, userEnd, handler, ANY_THROWABLE); mv.visitLabel(userStart); } @Override protected void onMethodEnd() { mv.visitLabel(userEnd); mv.visitLabel(handler); variable(Opcodes.ASTORE, instrumentedMethod.getReturnType().getStackSize().getSize()); storeDefaultReturn(); appendExit(); variable(Opcodes.ALOAD, instrumentedMethod.getReturnType().getStackSize().getSize()); mv.visitInsn(Opcodes.ATHROW); } /** * Stores a default return value in the designated slot of the local variable array. */ private void storeDefaultReturn() { if (instrumentedMethod.getReturnType().represents(boolean.class) || instrumentedMethod.getReturnType().represents(byte.class) || instrumentedMethod.getReturnType().represents(short.class) || instrumentedMethod.getReturnType().represents(char.class) || instrumentedMethod.getReturnType().represents(int.class)) { mv.visitInsn(Opcodes.ICONST_0); variable(Opcodes.ISTORE); } else if (instrumentedMethod.getReturnType().represents(long.class)) { mv.visitInsn(Opcodes.LCONST_0); variable(Opcodes.LSTORE); } else if (instrumentedMethod.getReturnType().represents(float.class)) { mv.visitInsn(Opcodes.FCONST_0); variable(Opcodes.FSTORE); } else if (instrumentedMethod.getReturnType().represents(double.class)) { mv.visitInsn(Opcodes.DCONST_0); variable(Opcodes.DSTORE); } else if (!instrumentedMethod.getReturnType().represents(void.class)) { mv.visitInsn(Opcodes.ACONST_NULL); variable(Opcodes.ASTORE); } } @Override public String toString() { return "Advice.AdviceVisitor.WithExceptionHandling{" + "instrumentedMethod=" + instrumentedMethod + '}'; } } /** * An advise visitor that does not capture exceptions. */ protected static class WithoutExceptionHandling extends AdviceVisitor { /** * Creates a new advise visitor that does not capture exceptions. * * @param methodVisitor The method visitor for the instrumented method. * @param instrumentedMethod A description of the instrumented method. * @param methodEnter The dispatcher to be used for method entry. * @param methodExit The dispatcher to be used for method exit. * @param binaryRepresentation The binary representation of the advise methods' class file. */ protected WithoutExceptionHandling(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod, Dispatcher.Resolved.ForMethodEnter methodEnter, Dispatcher.Resolved.ForMethodExit methodExit, byte[] binaryRepresentation) { super(methodVisitor, instrumentedMethod, methodEnter, methodExit, binaryRepresentation); } @Override protected void onMethodStart() { appendEnter(); } @Override protected void onMethodExit() { appendExit(); } @Override protected void onMethodEnd() { /* do nothing */ } @Override public String toString() { return "Advice.AdviceVisitor.WithoutExceptionHandling{" + "instrumentedMethod=" + instrumentedMethod + '}'; } } } /** * A dispatcher for implementing advise. */ protected interface Dispatcher { /** * Indicates that a method does not represent advise and does not need to be visited. */ MethodVisitor IGNORE_METHOD = null; /** * Returns {@code true} if this dispatcher is alive. * * @return {@code true} if this dispatcher is alive. */ boolean isAlive(); /** * Resolves this dispatcher as a dispatcher for entering a method. * * @return This dispatcher as a dispatcher for entering a method. */ Resolved.ForMethodEnter asMethodEnter(); /** * Resolves this dispatcher as a dispatcher for exiting a method. * * @param dispatcher The dispatcher for entering a method. * @return This dispatcher as a dispatcher for exiting a method. */ Resolved.ForMethodExit asMethodExitTo(Resolved.ForMethodEnter dispatcher); /** * Represents a resolved dispatcher. */ interface Resolved { /** * Applies this dispatcher for a method that is discovered in the advice class's class file. * * @param internalName The discovered method's internal name. * @param descriptor The discovered method's descriptor. * @param methodVisitor The method visitor for writing the instrumented method. * @param instrumentedMethod A description of the instrumented method. * @return A method visitor for reading the discovered method or {@code null} if the discovered method is of no interest. */ MethodVisitor apply(String internalName, String descriptor, MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod); /** * Represents a resolved dispatcher for entering a method. */ interface ForMethodEnter extends Resolved { /** * Returns the type that this dispatcher supplies as a result of its advise or a description of {@code void} if * no type is supplied as a result of the enter advise. * * @return The type that this dispatcher supplies as a result of its advise or a description of {@code void}. */ TypeDescription getEnterType(); } /** * Represents a resolved dispatcher for exiting a method. */ interface ForMethodExit extends Resolved { /** * Indicates if this advise requires to be called when the instrumented method terminates exceptionally. * * @return {@code true} if this advise requires to be called when the instrumented method terminates exceptionally. */ boolean isSkipThrowable(); } } /** * An implementation for inactive devise that does not write any byte code. */ enum Inactive implements Dispatcher, Resolved.ForMethodEnter, Resolved.ForMethodExit { /** * The singleton instance. */ INSTANCE; @Override public boolean isAlive() { return false; } @Override public boolean isSkipThrowable() { return true; } @Override public TypeDescription getEnterType() { return TypeDescription.VOID; } @Override public Resolved.ForMethodEnter asMethodEnter() { return this; } @Override public Resolved.ForMethodExit asMethodExitTo(Resolved.ForMethodEnter dispatcher) { return this; } @Override public MethodVisitor apply(String internalName, String descriptor, MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod) { return IGNORE_METHOD; } @Override public String toString() { return "Advice.Dispatcher.Inactive." + name(); } } /** * A dispatcher for active advise. */ class Active implements Dispatcher { /** * The advise method. */ protected final MethodDescription.InDefinedShape adviseMethod; /** * Creates a dispatcher for active advise. * * @param adviseMethod The advise method. */ protected Active(MethodDescription.InDefinedShape adviseMethod) { this.adviseMethod = adviseMethod; } @Override public boolean isAlive() { return true; } @Override public Dispatcher.Resolved.ForMethodEnter asMethodEnter() { return new Resolved.ForMethodEnter(adviseMethod); } @Override public Dispatcher.Resolved.ForMethodExit asMethodExitTo(Dispatcher.Resolved.ForMethodEnter dispatcher) { return new Resolved.ForMethodExit(adviseMethod, dispatcher.getEnterType()); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && adviseMethod.equals(((Active) other).adviseMethod); } @Override public int hashCode() { return adviseMethod.hashCode(); } @Override public String toString() { return "Advice.Dispatcher.Active{" + "adviseMethod=" + adviseMethod + '}'; } /** * A resolved version of a dispatcher. */ protected abstract static class Resolved implements Dispatcher.Resolved { /** * Indicates a read-only mapping for an offset. */ private static final boolean READ_ONLY = true; /** * The represented advise method. */ protected final MethodDescription.InDefinedShape adviseMethod; /** * An unresolved mapping of offsets of the advise method based on the annotations discovered on each method parameter. */ protected final Map offsetMappings; /** * Creates a new resolved version of a dispatcher. * * @param adviseMethod The represented advise method. * @param factory An unresolved mapping of offsets of the advise method based on the annotations discovered on each method parameter. */ protected Resolved(MethodDescription.InDefinedShape adviseMethod, OffsetMapping.Factory... factory) { this.adviseMethod = adviseMethod; offsetMappings = new HashMap(); for (ParameterDescription.InDefinedShape parameterDescription : adviseMethod.getParameters()) { OffsetMapping offsetMapping = OffsetMapping.Factory.UNDEFINED; for (OffsetMapping.Factory aFactory : factory) { OffsetMapping possible = aFactory.make(parameterDescription); if (possible != null) { if (offsetMapping == null) { offsetMapping = possible; } else { throw new IllegalStateException(parameterDescription + " is bound to both " + possible + " and " + offsetMapping); } } } offsetMappings.put(parameterDescription.getOffset(), offsetMapping == null ? new OffsetMapping.ForParameter(parameterDescription.getIndex(), READ_ONLY, parameterDescription.getType().asErasure()) : offsetMapping); } } @Override public MethodVisitor apply(String internalName, String descriptor, MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod) { return adviseMethod.getInternalName().equals(internalName) && adviseMethod.getDescriptor().equals(descriptor) ? apply(methodVisitor, instrumentedMethod) : IGNORE_METHOD; } /** * Applies a resolution for a given instrumented method. * * @param methodVisitor A method visitor for writing byte code to the instrumented method. * @param instrumentedMethod A description of the instrumented method. * @return A method visitor for visiting the advise method's byte code. */ protected abstract MethodVisitor apply(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod); @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; Resolved resolved = (Resolved) other; return adviseMethod.equals(resolved.adviseMethod) && offsetMappings.equals(resolved.offsetMappings); } @Override public int hashCode() { int result = adviseMethod.hashCode(); result = 31 * result + offsetMappings.hashCode(); return result; } /** * Represents an offset mapping for an advise method to an alternative offset. */ interface OffsetMapping { /** * Resolves an offset mapping to a given target offset. * * @param instrumentedMethod The instrumented method for which the mapping is to be resolved. * @param additionalSize The additional size to consider before writing to any slot. * @return A suitable target mapping. */ Target resolve(MethodDescription.InDefinedShape instrumentedMethod, StackSize additionalSize); /** * A target offset of an offset mapping. */ interface Target { /** * Applies this offset mapping for a {@link MethodVisitor#visitVarInsn(int, int)} instruction. * * @param methodVisitor The method visitor onto which this offset mapping is to be applied. * @param opcode The opcode of the original instruction. */ void apply(MethodVisitor methodVisitor, int opcode); /** * A read-write target mapping. */ class ForParameter implements Target { /** * The mapped offset. */ private final int offset; /** * Creates a new read-write target mapping. * * @param offset The mapped offset. */ protected ForParameter(int offset) { this.offset = offset; } @Override public void apply(MethodVisitor methodVisitor, int opcode) { methodVisitor.visitVarInsn(opcode, offset); } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ForParameter forParameter = (ForParameter) object; return offset == forParameter.offset; } @Override public int hashCode() { return offset; } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.Target.ForParameter{" + "offset=" + offset + '}'; } } /** * A read-only target mapping. */ class ForReadOnlyParameter implements Target { /** * The mapped offset. */ private final int offset; /** * Creates a new read-only target mapping. * * @param offset The mapped offset. */ protected ForReadOnlyParameter(int offset) { this.offset = offset; } @Override public void apply(MethodVisitor methodVisitor, int opcode) { switch (opcode) { case Opcodes.ISTORE: case Opcodes.LSTORE: case Opcodes.FSTORE: case Opcodes.DSTORE: case Opcodes.ASTORE: throw new IllegalStateException("Cannot write to read-only parameter at offset " + offset); case Opcodes.ILOAD: case Opcodes.LLOAD: case Opcodes.FLOAD: case Opcodes.DLOAD: case Opcodes.ALOAD: methodVisitor.visitVarInsn(opcode, offset); break; default: throw new IllegalArgumentException("Did not expect opcode: " + opcode); } } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ForReadOnlyParameter forReadOnlyParameter = (ForReadOnlyParameter) object; return offset == forReadOnlyParameter.offset; } @Override public int hashCode() { return offset; } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.Target.ForReadOnlyParameter{" + "offset=" + offset + '}'; } } /** * An offset mapping for a field. */ class ForField implements Target { /** * The field being read. */ private final FieldDescription fieldDescription; /** * Creates a new offset mapping for a field. * * @param fieldDescription The field being read. */ protected ForField(FieldDescription fieldDescription) { this.fieldDescription = fieldDescription; } @Override public void apply(MethodVisitor methodVisitor, int opcode) { switch (opcode) { case Opcodes.ISTORE: case Opcodes.ASTORE: case Opcodes.FSTORE: case Opcodes.LSTORE: case Opcodes.DSTORE: throw new IllegalStateException("Cannot write to field: " + fieldDescription); case Opcodes.ILOAD: case Opcodes.FLOAD: case Opcodes.ALOAD: case Opcodes.LLOAD: case Opcodes.DLOAD: int accessOpcode; if (fieldDescription.isStatic()) { accessOpcode = Opcodes.GETSTATIC; } else { methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); accessOpcode = Opcodes.GETFIELD; } methodVisitor.visitFieldInsn(accessOpcode, fieldDescription.getDeclaringType().asErasure().getInternalName(), fieldDescription.getInternalName(), fieldDescription.getDescriptor()); break; default: throw new IllegalArgumentException("Did not expect opcode: " + opcode); } } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ForField forField = (ForField) object; return fieldDescription.equals(forField.fieldDescription); } @Override public int hashCode() { return fieldDescription.hashCode(); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.Target.ForField{" + "fieldDescription=" + fieldDescription + '}'; } } } /** * Represents a factory for creating a {@link OffsetMapping} for a given parameter. */ interface Factory { /** * Indicates that an offset mapping is undefined. */ OffsetMapping UNDEFINED = null; /** * Creates a new offset mapping for the supplied parameter if possible. * * @param parameterDescription The parameter description for which to resolve an offset mapping. * @return A resolved offset mapping or {@code null} if no mapping can be resolved for this parameter. */ OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription); } /** * An offset mapping for a given parameter of the instrumented method. */ class ForParameter implements OffsetMapping { /** * The index of the parameter. */ private final int index; /** * Determines if the parameter is to be treated as read-only. */ private final boolean readOnly; /** * The type expected by the advise method. */ private final TypeDescription targetType; /** * Creates a new offset mapping for a parameter. * * @param argument The annotation for which the mapping is to be created. * @param targetType Determines if the parameter is to be treated as read-only. */ protected ForParameter(Argument argument, TypeDescription targetType) { this(argument.value(), argument.readOnly(), targetType); } /** * Creates a new offset mapping for a parameter of the instrumented method. * * @param index The index of the parameter. * @param readOnly Determines if the parameter is to be treated as read-only. * @param targetType The type expected by the advise method. */ protected ForParameter(int index, boolean readOnly, TypeDescription targetType) { this.index = index; this.readOnly = readOnly; this.targetType = targetType; } @Override public Target resolve(MethodDescription.InDefinedShape instrumentedMethod, StackSize additionalSize) { ParameterList parameters = instrumentedMethod.getParameters(); if (parameters.size() <= index) { throw new IllegalStateException(instrumentedMethod + " does not define an index " + index); } else if (!readOnly && !parameters.get(index).getType().asErasure().equals(targetType)) { throw new IllegalStateException("read-only " + targetType + " is not equal to type of " + parameters.get(index)); } else if (readOnly && !parameters.get(index).getType().asErasure().isAssignableTo(targetType)) { throw new IllegalStateException(targetType + " is not assignable to " + parameters.get(index)); } return readOnly ? new Target.ForReadOnlyParameter(parameters.get(index).getOffset()) : new Target.ForParameter(parameters.get(index).getOffset()); } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ForParameter that = (ForParameter) object; return index == that.index && readOnly == that.readOnly && targetType.equals(that.targetType); } @Override public int hashCode() { int result = index; result = 31 * result + (readOnly ? 1 : 0); result = 31 * result + targetType.hashCode(); return result; } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForParameter{" + "index=" + index + ", readOnly=" + readOnly + ", targetType=" + targetType + '}'; } /** * A factory for creating a {@link ForParameter} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription) { AnnotationDescription.Loadable annotation = parameterDescription.getDeclaredAnnotations().ofType(Argument.class); return annotation == null ? UNDEFINED : new ForParameter(annotation.loadSilent(), parameterDescription.getType().asErasure()); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForParameter.Factory." + name(); } } } /** * An offset mapping that provides access to the {@code this} reference of the instrumented method. */ class ForThisReference implements OffsetMapping { /** * The offset of the this reference in a Java method. */ private static final int THIS_REFERENCE = 0; /** * Determines if the parameter is to be treated as read-only. */ private final boolean readOnly; /** * The type that the advise method expects for the {@code this} reference. */ private final TypeDescription targetType; /** * Creates a new offset mapping for a {@code this} reference. * * @param readOnly Determines if the parameter is to be treated as read-only. * @param targetType The type that the advise method expects for the {@code this} reference. */ protected ForThisReference(boolean readOnly, TypeDescription targetType) { this.readOnly = readOnly; this.targetType = targetType; } @Override public Target resolve(MethodDescription.InDefinedShape instrumentedMethod, StackSize additionalSize) { if (instrumentedMethod.isStatic()) { throw new IllegalStateException("Cannot map this reference for static method " + instrumentedMethod); } else if (!readOnly && !instrumentedMethod.getDeclaringType().equals(targetType)) { throw new IllegalStateException("Declaring type of " + instrumentedMethod + " is not equal to read-only " + targetType); } else if (readOnly && !instrumentedMethod.getDeclaringType().isAssignableTo(targetType)) { throw new IllegalStateException("Declaring type of " + instrumentedMethod + " is not assignable to " + targetType); } return readOnly ? new Target.ForReadOnlyParameter(THIS_REFERENCE) : new Target.ForParameter(THIS_REFERENCE); } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ForThisReference that = (ForThisReference) object; return readOnly == that.readOnly && targetType.equals(that.targetType); } @Override public int hashCode() { int result = (readOnly ? 1 : 0); result = 31 * result + targetType.hashCode(); return result; } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForThisReference{" + "readOnly=" + readOnly + ", targetType=" + targetType + '}'; } /** * A factory for creating a {@link ForThisReference} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription) { AnnotationDescription.Loadable annotation = parameterDescription.getDeclaredAnnotations().ofType(This.class); return annotation == null ? UNDEFINED : new ForThisReference(annotation.loadSilent().readOnly(), parameterDescription.getType().asErasure()); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForThisReference.Factory." + name(); } } } /** * An offset mapping for a field. */ abstract class ForField implements OffsetMapping { /** * The name of the field. */ protected final String name; /** * The expected type that the field can be assigned to. */ protected final TypeDescription targetType; /** * Creates an offset mapping for a field. * * @param name The name of the field. * @param targetType The expected type that the field can be assigned to. */ protected ForField(String name, TypeDescription targetType) { this.name = name; this.targetType = targetType; } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; ForField forField = (ForField) object; return name.equals(forField.name) && targetType.equals(forField.targetType); } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + targetType.hashCode(); return result; } @Override public Target resolve(MethodDescription.InDefinedShape instrumentedMethod, StackSize additionalSize) { FieldLocator.Resolution resolution = fieldLocator(instrumentedMethod.getDeclaringType()).locate(name); if (!resolution.isResolved()) { throw new IllegalStateException("Cannot locate field named " + name + " for " + instrumentedMethod); } else if (!resolution.getFieldDescription().getType().asErasure().isAssignableTo(targetType)) { throw new IllegalStateException("Cannot assign type of field " + resolution.getFieldDescription() + " to " + targetType); } else if (!resolution.getFieldDescription().isStatic() && instrumentedMethod.isStatic()) { throw new IllegalStateException("Cannot read non-static field " + resolution.getFieldDescription() + " from static method " + instrumentedMethod); } return new Target.ForField(resolution.getFieldDescription()); } /** * 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. */ protected static class WithImplicitType extends ForField { /** * Creates an offset mapping for a field with an implicit declaring type. * * @param name The name of the field. * @param targetType The expected type that the field can be assigned to. */ protected WithImplicitType(String name, TypeDescription targetType) { super(name, targetType); } @Override protected FieldLocator fieldLocator(TypeDescription instrumentedType) { return new FieldLocator.ForClassHierarchy(instrumentedType); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForField.WithImplicitType{" + "name=" + name + ", targetType=" + targetType + '}'; } } /** * An offset mapping for a field with an explicit declaring type. */ protected static class WithExplicitType extends ForField { /** * The type declaring the field. */ private final TypeDescription explicitType; /** * Creates an offset mapping for a field with an explicit declaring type. * * @param name The name of the field. * @param targetType The expected type that the field can be assigned to. * @param locatedType The type declaring the field. */ protected WithExplicitType(String name, TypeDescription targetType, TypeDescription locatedType) { super(name, targetType); this.explicitType = locatedType; } @Override protected FieldLocator fieldLocator(TypeDescription instrumentedType) { if (!instrumentedType.isAssignableTo(explicitType)) { throw new IllegalStateException(explicitType + " is no super type of " + instrumentedType); } return new FieldLocator.ForExactType(explicitType); } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; if (!super.equals(object)) return false; WithExplicitType that = (WithExplicitType) object; return explicitType.equals(that.explicitType); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + explicitType.hashCode(); return result; } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForField.WithExplicitType{" + "name=" + name + ", targetType=" + targetType + ", explicitType=" + explicitType + '}'; } } /** * A factory for a {@link ForField} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; /** * Creates a new factory for a {@link ForField} offset mapping. */ Factory() { MethodList methods = new TypeDescription.ForLoadedType(FieldValue.class).getDeclaredMethods(); value = methods.filter(named("value")).getOnly(); definingType = methods.filter(named("definingType")).getOnly(); } /** * The {@link FieldValue#value()} method. */ private final MethodDescription.InDefinedShape value; /** * The {@link FieldValue#definingType()}} method. */ private final MethodDescription.InDefinedShape definingType; @Override public OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription) { AnnotationDescription annotation = parameterDescription.getDeclaredAnnotations().ofType(FieldValue.class); if (annotation == null) { return UNDEFINED; } else { TypeDescription definingType = annotation.getValue(this.definingType, TypeDescription.class); String name = annotation.getValue(value, String.class); TypeDescription targetType = parameterDescription.getType().asErasure(); return definingType.represents(void.class) ? new WithImplicitType(name, targetType) : new WithExplicitType(name, targetType, definingType); } } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForField.Factory." + name(); } } } /** * An offset mapping that provides access to the value that is returned by the enter advise. */ enum ForEnterValue implements OffsetMapping { /** * Enables writing to the mapped offset. */ WRITABLE(false), /** * Only allows for reading the mapped offset. */ READ_ONLY(true); /** * Determines if the parameter is to be treated as read-only. */ private final boolean readOnly; /** * Creates a new offset mapping for an enter value. * * @param readOnly Determines if the parameter is to be treated as read-only. */ ForEnterValue(boolean readOnly) { this.readOnly = readOnly; } /** * Resolves an offset mapping for an enter value. * * @param readOnly {@code true} if the value is to be treated as read-only. * @return An appropriate offset mapping. */ public static OffsetMapping of(boolean readOnly) { return readOnly ? READ_ONLY : WRITABLE; } @Override public Target resolve(MethodDescription.InDefinedShape instrumentedMethod, StackSize additionalSize) { return readOnly ? new Target.ForReadOnlyParameter(instrumentedMethod.getStackSize()) : new Target.ForParameter(instrumentedMethod.getStackSize()); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForEnterValue." + name(); } /** * A factory for creating a {@link ForEnterValue} offset mapping. */ protected static class Factory implements OffsetMapping.Factory { /** * The supplied type of the enter method. */ private final TypeDescription enterType; /** * Creates a new factory for creating a {@link ForEnterValue} offset mapping. * * @param enterType The supplied type of the enter method. */ protected Factory(TypeDescription enterType) { this.enterType = enterType; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription) { AnnotationDescription.Loadable annotation = parameterDescription.getDeclaredAnnotations().ofType(Enter.class); if (annotation != null) { boolean readOnly = annotation.loadSilent().readOnly(); if (!readOnly && !enterType.equals(parameterDescription.getType().asErasure())) { throw new IllegalStateException("read-only type of " + parameterDescription + " does not equal " + enterType); } else if (readOnly && !enterType.isAssignableTo(parameterDescription.getType().asErasure())) { throw new IllegalStateException("Cannot assign the type of " + parameterDescription + " to supplied type " + enterType); } return ForEnterValue.of(readOnly); } else { return UNDEFINED; } } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && enterType.equals(((Factory) other).enterType); } @Override public int hashCode() { return enterType.hashCode(); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForEnterValue.Factory{" + "enterType=" + enterType + '}'; } } } /** * An offset mapping that provides access to the value that is returned by the instrumented method. */ class ForReturnValue implements OffsetMapping { /** * Determines if the parameter is to be treated as read-only. */ private final boolean readOnly; /** * The type that the advise method expects for the {@code this} reference. */ private final TypeDescription targetType; /** * Creates an offset mapping for accessing the return type of the instrumented method. * * @param readOnly Determines if the parameter is to be treated as read-only. * @param targetType The expected target type of the return type. */ protected ForReturnValue(boolean readOnly, TypeDescription targetType) { this.readOnly = readOnly; this.targetType = targetType; } @Override public Target resolve(MethodDescription.InDefinedShape instrumentedMethod, StackSize additionalSize) { if (!readOnly && !instrumentedMethod.getReturnType().asErasure().equals(targetType)) { throw new IllegalStateException("read-only return type of " + instrumentedMethod + " is not equal to " + targetType); } else if (readOnly && !instrumentedMethod.getReturnType().asErasure().isAssignableTo(targetType)) { throw new IllegalStateException("Cannot assign return type of " + instrumentedMethod + " to " + targetType); } return readOnly ? new Target.ForReadOnlyParameter(instrumentedMethod.getStackSize() + additionalSize.getSize()) : new Target.ForParameter(instrumentedMethod.getStackSize() + additionalSize.getSize()); } @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; ForReturnValue that = (ForReturnValue) other; return readOnly == that.readOnly && targetType.equals(that.targetType); } @Override public int hashCode() { return (readOnly ? 1 : 0) + 31 * targetType.hashCode(); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForReturnValue{" + "readOnly=" + readOnly + ", targetType=" + targetType + '}'; } /** * A factory for creating a {@link ForReturnValue} offset mapping. */ protected enum Factory implements OffsetMapping.Factory { /** * The singleton instance. */ INSTANCE; @Override public OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription) { AnnotationDescription.Loadable annotation = parameterDescription.getDeclaredAnnotations().ofType(Return.class); return annotation == null ? UNDEFINED : new ForReturnValue(annotation.loadSilent().readOnly(), parameterDescription.getType().asErasure()); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForReturnValue.Factory." + name(); } } } /** * An offset mapping for accessing a {@link Throwable} of the instrumented method. */ enum ForThrowable implements OffsetMapping, Factory { /** * The singleton instance. */ INSTANCE; @Override public OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription) { if (parameterDescription.getDeclaredAnnotations().isAnnotationPresent(Thrown.class)) { if (!parameterDescription.getType().represents(Throwable.class)) { throw new IllegalStateException("Parameter must be of type Throwable for " + parameterDescription); } return this; } else { return UNDEFINED; } } @Override public Target resolve(MethodDescription.InDefinedShape instrumentedMethod, StackSize additionalSize) { return new Target.ForParameter(instrumentedMethod.getStackSize() + additionalSize.getSize() + instrumentedMethod.getReturnType().getStackSize().getSize()); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.ForThrowable." + name(); } } /** * Represents a factory that throws an exception for a given set of illegal parameter annotations. */ class Illegal implements Factory { /** * The set of illegal annotations. */ private final List> annotations; /** * Creates a new factory for restricting the use of illegal annotation types. * * @param annotation The set of illegal annotations. */ //@SafeVarargs protected Illegal(Class... annotation) { this(Arrays.asList(annotation)); } /** * Creates a new factory for restricting the use of illegal annotation types. * * @param annotations The set of illegal annotations. */ protected Illegal(List> annotations) { this.annotations = annotations; } @Override public OffsetMapping make(ParameterDescription.InDefinedShape parameterDescription) { for (Class annotation : annotations) { if (parameterDescription.getDeclaredAnnotations().isAnnotationPresent(annotation)) { throw new IllegalStateException("Illegal annotation " + annotation + " for " + parameterDescription); } } return UNDEFINED; } @Override public boolean equals(Object other) { if (this == other) return true; if (other == null || getClass() != other.getClass()) return false; Illegal illegal = (Illegal) other; return annotations.equals(illegal.annotations); } @Override public int hashCode() { return annotations.hashCode(); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.OffsetMapping.Illegal{" + "annotations=" + annotations + '}'; } } } /** * A resolved dispatcher for implementing method enter advise. */ protected static class ForMethodEnter extends Resolved implements Dispatcher.Resolved.ForMethodEnter { /** * The {@code suppress} property of the {@link OnMethodEnter} type. */ private static final MethodDescription.InDefinedShape SUPPRESS; /* * Extracts the suppress property. */ static { SUPPRESS = new TypeDescription.ForLoadedType(OnMethodEnter.class).getDeclaredMethods().filter(named("suppress")).getOnly(); } /** * Creates a new resolved dispatcher for implementing method enter advise. * * @param adviseMethod The represented advise method. */ @SuppressWarnings("all") // In absence of @SafeVarargs for Java 6 protected ForMethodEnter(MethodDescription.InDefinedShape adviseMethod) { super(adviseMethod, OffsetMapping.ForParameter.Factory.INSTANCE, OffsetMapping.ForThisReference.Factory.INSTANCE, OffsetMapping.ForField.Factory.INSTANCE, new OffsetMapping.Illegal(Thrown.class, Enter.class, Return.class)); } @Override public TypeDescription getEnterType() { return adviseMethod.getReturnType().asErasure(); } @Override protected MethodVisitor apply(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod) { Map offsetMappings = new HashMap(); for (Map.Entry entry : this.offsetMappings.entrySet()) { offsetMappings.put(entry.getKey(), entry.getValue().resolve(instrumentedMethod, StackSize.ZERO)); } return new CodeTranslationVisitor.ReturnValueRetaining(methodVisitor, instrumentedMethod, adviseMethod, offsetMappings, adviseMethod.getDeclaredAnnotations().ofType(OnMethodEnter.class).getValue(SUPPRESS, TypeDescription.class)); } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.ForMethodEnter{" + "adviseMethod=" + adviseMethod + ", offsetMappings=" + offsetMappings + '}'; } } /** * A resolved dispatcher for implementing method exit advise. */ protected static class ForMethodExit extends Resolved implements Dispatcher.Resolved.ForMethodExit { /** * The {@code suppress} method of the {@link OnMethodExit} annotation. */ private static final MethodDescription.InDefinedShape SUPPRESS; /* * Extracts the suppress method. */ static { SUPPRESS = new TypeDescription.ForLoadedType(OnMethodExit.class).getDeclaredMethods().filter(named("suppress")).getOnly(); } /** * The additional stack size to consider when accessing the local variable array. */ private final StackSize additionalSize; /** * Creates a new resolved dispatcher for implementing method exit advise. * * @param adviseMethod The represented advise method. * @param enterType The type of the value supplied by the enter advise method or a description of {@code void} if * no such value exists. */ @SuppressWarnings("all") // In absence of @SafeVarargs for Java 6 protected ForMethodExit(MethodDescription.InDefinedShape adviseMethod, TypeDescription enterType) { super(adviseMethod, OffsetMapping.ForParameter.Factory.INSTANCE, OffsetMapping.ForThisReference.Factory.INSTANCE, OffsetMapping.ForField.Factory.INSTANCE, new OffsetMapping.ForEnterValue.Factory(enterType), OffsetMapping.ForReturnValue.Factory.INSTANCE, adviseMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).loadSilent().onThrowable() ? OffsetMapping.ForThrowable.INSTANCE : new OffsetMapping.Illegal(Thrown.class)); additionalSize = enterType.getStackSize(); } @Override public boolean isSkipThrowable() { return !adviseMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).loadSilent().onThrowable(); } @Override protected MethodVisitor apply(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod) { Map offsetMappings = new HashMap(); for (Map.Entry entry : this.offsetMappings.entrySet()) { offsetMappings.put(entry.getKey(), entry.getValue().resolve(instrumentedMethod, additionalSize)); } return new CodeTranslationVisitor.ReturnValueDiscarding(methodVisitor, instrumentedMethod, adviseMethod, offsetMappings, adviseMethod.getDeclaredAnnotations().ofType(OnMethodExit.class).getValue(SUPPRESS, TypeDescription.class), additionalSize); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && super.equals(other) && additionalSize == ((Resolved.ForMethodExit) other).additionalSize; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + additionalSize.hashCode(); return result; } @Override public String toString() { return "Advice.Dispatcher.Active.Resolved.ForMethodExit{" + "adviseMethod=" + adviseMethod + ", offsetMappings=" + offsetMappings + ", additionalSize=" + additionalSize + '}'; } } } /** * A producer for a default return value if this is applicable. */ interface ReturnValueProducer { /** * Sets a default return value for an advised method. * * @param methodVisitor The instrumented method's method visitor. */ void makeDefault(MethodVisitor methodVisitor); } /** * A visitor for translating an advise method's byte code for inlining into the instrumented method. */ protected abstract static class CodeTranslationVisitor extends MethodVisitor implements ReturnValueProducer { /** * Indicates that an annotation should not be read. */ private static final AnnotationVisitor IGNORE_ANNOTATION = null; /** * The instrumented method. */ protected final MethodDescription.InDefinedShape instrumentedMethod; /** * The advise method. */ protected final MethodDescription.InDefinedShape adviseMethod; /** * A mapping of offsets to resolved target offsets in the instrumented method. */ private final Map offsetMappings; /** * A handler for optionally suppressing exceptions. */ private final SuppressionHandler suppressionHandler; /** * A label indicating the end of the advise byte code. */ protected final Label endOfMethod; /** * Creates a new code translation visitor. * * @param methodVisitor A method visitor for writing the instrumented method's byte code. * @param instrumentedMethod The instrumented method. * @param adviseMethod The advise method. * @param offsetMappings A mapping of offsets to resolved target offsets in the instrumented method. * @param throwableType A throwable type to be suppressed or {@link NoSuppression} if no suppression should be applied. */ protected CodeTranslationVisitor(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod, MethodDescription.InDefinedShape adviseMethod, Map offsetMappings, TypeDescription throwableType) { super(Opcodes.ASM5, methodVisitor); this.instrumentedMethod = instrumentedMethod; this.adviseMethod = adviseMethod; this.offsetMappings = offsetMappings; suppressionHandler = throwableType.represents(NoSuppression.class) ? SuppressionHandler.NoOp.INSTANCE : new SuppressionHandler.Suppressing(throwableType); endOfMethod = new Label(); } @Override public void visitParameter(String name, int modifiers) { /* do nothing */ } @Override public AnnotationVisitor visitAnnotationDefault() { return IGNORE_ANNOTATION; } @Override public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { return IGNORE_ANNOTATION; } @Override public AnnotationVisitor visitTypeAnnotation(int typeReference, TypePath typePath, String descriptor, boolean visible) { return IGNORE_ANNOTATION; } @Override public AnnotationVisitor visitParameterAnnotation(int index, String descriptor, boolean visible) { return IGNORE_ANNOTATION; } @Override public void visitAttribute(Attribute attr) { /* do nothing */ } @Override public void visitCode() { suppressionHandler.onStart(mv, endOfMethod); } @Override public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { /* do nothing */ } @Override public void visitLineNumber(int line, Label start) { /* do nothing */ } @Override public void visitEnd() { mv.visitLabel(endOfMethod); suppressionHandler.onEnd(mv, this); } @Override public void visitMaxs(int maxStack, int maxLocals) { /* do nothing */ } @Override public void visitVarInsn(int opcode, int offset) { Resolved.OffsetMapping.Target target = offsetMappings.get(offset); if (target != null) { target.apply(mv, opcode); } else { mv.visitVarInsn(opcode, adjust(offset + instrumentedMethod.getStackSize() - adviseMethod.getStackSize())); } } /** * Adjusts the offset of a variable instruction within the advise method such that no arguments to * the instrumented method are overridden. * * @param offset The original offset. * @return The adjusted offset. */ protected abstract int adjust(int offset); @Override public abstract void visitInsn(int opcode); /** * A suppression handler for optionally suppressing exceptions. */ protected interface SuppressionHandler { /** * Invoked at the start of a method. * * @param methodVisitor The method visitor of the instrumented method. * @param endOfMethod A label indicating the end of the method. */ void onStart(MethodVisitor methodVisitor, Label endOfMethod); /** * Invoked at the end of a method. * * @param methodVisitor The method visitor of the instrumented method. * @param returnValueProducer A producer for defining a default return value of the advised method. */ void onEnd(MethodVisitor methodVisitor, ReturnValueProducer returnValueProducer); /** * A non-operational suppression handler that does not suppress any method. */ enum NoOp implements SuppressionHandler { /** * The singleton instance. */ INSTANCE; @Override public void onStart(MethodVisitor methodVisitor, Label endOfMethod) { /* do nothing */ } @Override public void onEnd(MethodVisitor methodVisitor, ReturnValueProducer returnValueProducer) { /* do nothing */ } @Override public String toString() { return "Advice.Dispatcher.Active.CodeTranslationVisitor.SuppressionHandler.NoOp." + name(); } } /** * A suppression handler that suppresses a given throwable type. */ class Suppressing implements SuppressionHandler { /** * The suppressed throwable type. */ private final TypeDescription throwableType; /** * A label indicating the start of the method. */ private final Label startOfMethod; /** * A label indicating the exception handler. */ private final Label handler; /** * Creates a new suppressing suppression handler. * * @param throwableType The suppressed throwable type. */ protected Suppressing(TypeDescription throwableType) { this.throwableType = throwableType; startOfMethod = new Label(); handler = new Label(); } @Override public void onStart(MethodVisitor methodVisitor, Label endOfMethod) { methodVisitor.visitTryCatchBlock(startOfMethod, endOfMethod, handler, throwableType.getInternalName()); methodVisitor.visitLabel(startOfMethod); } @Override public void onEnd(MethodVisitor methodVisitor, ReturnValueProducer returnValueProducer) { Label endOfHandler = new Label(); methodVisitor.visitJumpInsn(Opcodes.GOTO, endOfHandler); methodVisitor.visitLabel(handler); methodVisitor.visitInsn(Opcodes.POP); returnValueProducer.makeDefault(methodVisitor); methodVisitor.visitLabel(endOfHandler); } @Override public boolean equals(Object object) { if (this == object) return true; if (object == null || getClass() != object.getClass()) return false; Suppressing that = (Suppressing) object; return throwableType.equals(that.throwableType); } @Override public int hashCode() { return throwableType.hashCode(); } @Override public String toString() { return "Advice.Dispatcher.Active.CodeTranslationVisitor.SuppressionHandler.Suppressing{" + "throwableType=" + throwableType + ", startOfMethod=" + startOfMethod + ", handler=" + handler + '}'; } } } /** * A code translation visitor that retains the return value of the represented advise method. */ protected static class ReturnValueRetaining extends CodeTranslationVisitor { /** * Creates a new code translation visitor that retains the return value of the enter advise. * * @param methodVisitor A method visitor for writing the instrumented method's byte code. * @param instrumentedMethod The instrumented method. * @param adviseMethod The advise method. * @param offsetMappings A mapping of offsets of the advise methods to their corresponding offsets in the instrumented method. * @param throwableType A throwable type to be suppressed or {@link NoSuppression} if no suppression should be applied. */ protected ReturnValueRetaining(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod, MethodDescription.InDefinedShape adviseMethod, Map offsetMappings, TypeDescription throwableType) { super(methodVisitor, instrumentedMethod, adviseMethod, offsetMappings, throwableType); } @Override public void visitInsn(int opcode) { switch (opcode) { case Opcodes.RETURN: break; case Opcodes.IRETURN: mv.visitVarInsn(Opcodes.ISTORE, instrumentedMethod.getStackSize()); break; case Opcodes.LRETURN: mv.visitVarInsn(Opcodes.LSTORE, instrumentedMethod.getStackSize()); break; case Opcodes.ARETURN: mv.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize()); break; case Opcodes.FRETURN: mv.visitVarInsn(Opcodes.FSTORE, instrumentedMethod.getStackSize()); break; case Opcodes.DRETURN: mv.visitVarInsn(Opcodes.DSTORE, instrumentedMethod.getStackSize()); break; default: mv.visitInsn(opcode); return; } mv.visitJumpInsn(Opcodes.GOTO, endOfMethod); } @Override protected int adjust(int offset) { return offset; } @Override public void makeDefault(MethodVisitor methodVisitor) { if (adviseMethod.getReturnType().represents(boolean.class) || adviseMethod.getReturnType().represents(byte.class) || adviseMethod.getReturnType().represents(short.class) || adviseMethod.getReturnType().represents(char.class) || adviseMethod.getReturnType().represents(int.class)) { methodVisitor.visitInsn(Opcodes.ICONST_0); methodVisitor.visitVarInsn(Opcodes.ISTORE, instrumentedMethod.getStackSize()); } else if (adviseMethod.getReturnType().represents(long.class)) { methodVisitor.visitInsn(Opcodes.LCONST_0); methodVisitor.visitVarInsn(Opcodes.LSTORE, instrumentedMethod.getStackSize()); } else if (adviseMethod.getReturnType().represents(float.class)) { methodVisitor.visitInsn(Opcodes.FCONST_0); methodVisitor.visitVarInsn(Opcodes.FSTORE, instrumentedMethod.getStackSize()); } else if (adviseMethod.getReturnType().represents(double.class)) { methodVisitor.visitInsn(Opcodes.DCONST_0); methodVisitor.visitVarInsn(Opcodes.DSTORE, instrumentedMethod.getStackSize()); } else if (!adviseMethod.getReturnType().represents(void.class)) { methodVisitor.visitInsn(Opcodes.ACONST_NULL); methodVisitor.visitVarInsn(Opcodes.ASTORE, instrumentedMethod.getStackSize()); } } @Override public String toString() { return "Advice.Dispatcher.Active.CodeTranslationVisitor.ReturnValueRetaining{" + "instrumentedMethod=" + instrumentedMethod + ", adviseMethod=" + adviseMethod + '}'; } } /** * A code translation visitor that discards the return value of the represented advise method. */ protected static class ReturnValueDiscarding extends CodeTranslationVisitor { /** * The size of the exception slot for the instrumented method's potential exception. */ private static final int EXCEPTION_SIZE = 1; /** * An additional size of the local variable array to consider when writing or reading values. */ private final StackSize additionalVariableSize; /** * Creates a new code translation visitor that retains the return value of the enter advise. * * @param methodVisitor A method visitor for writing the instrumented method's byte code. * @param instrumentedMethod The instrumented method. * @param adviseMethod The advise method. * @param offsetMappings A mapping of offsets of the advise methods to their corresponding offsets in the instrumented method. * @param throwableType A throwable type to be suppressed or {@link NoSuppression} if no suppression should be applied. * @param additionalVariableSize An additional size of the local variable array to consider when writing or reading values. */ protected ReturnValueDiscarding(MethodVisitor methodVisitor, MethodDescription.InDefinedShape instrumentedMethod, MethodDescription.InDefinedShape adviseMethod, Map offsetMappings, TypeDescription throwableType, StackSize additionalVariableSize) { super(methodVisitor, instrumentedMethod, adviseMethod, offsetMappings, throwableType); this.additionalVariableSize = additionalVariableSize; } @Override public void visitInsn(int opcode) { switch (opcode) { case Opcodes.RETURN: break; case Opcodes.IRETURN: case Opcodes.ARETURN: case Opcodes.FRETURN: mv.visitInsn(Opcodes.POP); break; case Opcodes.LRETURN: case Opcodes.DRETURN: mv.visitInsn(Opcodes.POP2); break; default: mv.visitInsn(opcode); return; } mv.visitJumpInsn(Opcodes.GOTO, endOfMethod); } @Override protected int adjust(int offset) { return offset + instrumentedMethod.getReturnType().getStackSize().getSize() + additionalVariableSize.getSize() + EXCEPTION_SIZE; } @Override public void makeDefault(MethodVisitor methodVisitor) { /* do nothing */ } @Override public String toString() { return "Advice.Dispatcher.Active.CodeTranslationVisitor.ReturnValueDiscarding{" + "instrumentedMethod=" + instrumentedMethod + ", adviseMethod=" + adviseMethod + ", additionalVariableSize=" + additionalVariableSize + '}'; } } } } } /** *

* Indicates that this method should be inlined before the matched method is invoked. Any class must declare * at most one method with this annotation. The annotated method must be static. When instrumenting constructors, * the {@code this} values can only be accessed for writing fields but not for reading fields or invoking methods. *

*

* The annotated method can return a value that is made accessible to another method annotated by {@link OnMethodExit}. *

* * @see Advice * @see Argument * @see This */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnMethodEnter { /** * Indicates that this advice should suppress any {@link Throwable} type being thrown during the advice's execution. * * @return The type of {@link Throwable} to suppress. */ Class suppress() default NoSuppression.class; } /** *

* Indicates that this method should be inlined before the matched method is invoked. Any class must declare * at most one method with this annotation. The annotated method must be static. *

*

* The annotated method can imply to not be invoked when the instrumented method terminates exceptionally by * setting the {@link OnMethodExit#onThrowable()} property. *

* * @see Advice * @see Argument * @see This * @see Enter * @see Return * @see Thrown */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface OnMethodExit { /** * Indicates that the advise method should also be called when a method terminates exceptionally. * * @return {@code true} if the advise method should be invoked when a method terminates exceptionally. */ boolean onThrowable() default true; /** * Indicates that this advice should suppress any {@link Throwable} type being thrown during the advice's execution. * * @return The type of {@link Throwable} to suppress. */ Class suppress() default NoSuppression.class; } /** * Indicates that the annotated parameter should be mapped to the parameter with index {@link Argument#value()} of * the instrumented method. * * @see Advice * @see OnMethodEnter * @see OnMethodExit */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Argument { /** * Returns the index of the mapped parameter. * * @return The index of the mapped parameter. */ int value(); /** * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated * type must be equal to the parameter of the instrumented method. If this property is set to {@code true}, the * annotated parameter can be any super type of the instrumented methods parameter. * * @return {@code true} if this parameter is read-only. */ boolean readOnly() default true; } /** * Indicates that the annotated parameter should be mapped to the {@code this} reference of the instrumented method. * * @see Advice * @see OnMethodEnter * @see OnMethodExit */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface This { /** * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated * type must be equal to the parameter of the instrumented method. If this property is set to {@code true}, the * annotated parameter can be any super type of the instrumented methods parameter. * * @return {@code true} if this parameter is read-only. */ boolean readOnly() default true; } /** * Indicates that the annotated parameter should be mapped to a field in the scope of the instrumented method. All * field references are always read-only. * * @see Advice * @see OnMethodEnter * @see OnMethodExit */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface FieldValue { /** * Returns the name of the field. * * @return The name of the field. */ String value(); /** * Returns the type that declares the field that should be mapped to the annotated parameter. If this property * is set to {@code void}, the field is looked up implicitly within the instrumented class's class hierarchy. * * @return The type that declares the field or {@code void} if this type should be determined implicitly. */ Class definingType() default void.class; } /** * Indicates that the annotated parameter should be mapped to the value that is returned by the advise method that is annotated * by {@link OnMethodEnter}. * * @see Advice * @see OnMethodExit */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Enter { /** * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated * type must be equal to the parameter of the instrumented method. If this property is set to {@code true}, the * annotated parameter can be any super type of the instrumented methods parameter. * * @return {@code true} if this parameter is read-only. */ boolean readOnly() default true; } /** * Indicates that the annotated parameter should be mapped to the return value of the instrumented method. If the instrumented * method terminates exceptionally, the type's default value is assigned to the parameter, i.e. {@code 0} for numeric types * and {@code null} for reference types. * * @see Advice * @see OnMethodExit */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Return { /** * Indicates if it is possible to write to this parameter. If this property is set to {@code false}, the annotated * type must be equal to the parameter of the instrumented method. If this property is set to {@code true}, the * annotated parameter can be any super type of the instrumented methods parameter. * * @return {@code true} if this parameter is read-only. */ boolean readOnly() default true; } /** * Indicates that the annotated parameter should be mapped to the return value of the instrumented method. For this to be valid, * the parameter must be of type {@link Throwable}. If the instrumented method terminates regularly, {@code null} is assigned to * the annotated parameter. * * @see Advice * @see OnMethodExit */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface Thrown { /* empty */ } /** * A marker class that indicates that an advice method does not suppress any {@link Throwable}. */ private static class NoSuppression extends Throwable { /** * A private constructor as this class is not supposed to be invoked. */ private NoSuppression() { throw new UnsupportedOperationException("This marker class is not supposed to be instantiated"); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy