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

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

There is a newer version: 1.36.0
Show 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.description.ByteCodeElement;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.description.type.TypeList;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.scaffold.MethodGraph;
import net.bytebuddy.implementation.Implementation;
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.assign.Assigner;
import net.bytebuddy.implementation.bytecode.constant.DefaultValue;
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.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.utility.CompoundList;
import net.bytebuddy.utility.OpenedClassReader;
import net.bytebuddy.utility.nullability.MaybeNull;
import net.bytebuddy.utility.visitor.LocalVariableAwareMethodVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

import static net.bytebuddy.matcher.ElementMatchers.*;

/**
 * 

* Substitutes field access or method invocations within a method's body. *

*

* Important: This component relies on using a {@link TypePool} for locating types within method bodies. Within a redefinition * or a rebasement, this type pool normally resolved correctly by Byte Buddy. When subclassing a type, the type pool must be set * explicitly, using {@link net.bytebuddy.dynamic.DynamicType.Builder#make(TypePool)} or any similar method. It is however not normally * necessary to use this component when subclassing a type where methods are only defined explicitly. *

*/ @HashCodeAndEqualsPlugin.Enhance public class MemberSubstitution implements AsmVisitorWrapper.ForDeclaredMethods.MethodVisitorWrapper { /** * The method graph compiler to use. */ private final MethodGraph.Compiler methodGraphCompiler; /** * {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. */ private final boolean strict; /** * The type pool resolver to use. */ private final TypePoolResolver typePoolResolver; /** * The replacement factory to use. */ private final Replacement.Factory replacementFactory; /** * Creates a default member substitution. * * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. */ protected MemberSubstitution(boolean strict) { this(MethodGraph.Compiler.DEFAULT, TypePoolResolver.OfImplicitPool.INSTANCE, strict, Replacement.NoOp.INSTANCE); } /** * Creates a new member substitution. * * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacementFactory The replacement factory to use. */ protected MemberSubstitution(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory) { this.methodGraphCompiler = methodGraphCompiler; this.typePoolResolver = typePoolResolver; this.strict = strict; this.replacementFactory = replacementFactory; } /** * Creates a member substitution that requires the resolution of all fields and methods that are referenced within a method body. Doing so, * this component raises an exception if any member cannot be resolved what makes this component unusable when facing optional types. * * @return A strict member substitution. */ public static MemberSubstitution strict() { return new MemberSubstitution(true); } /** * Creates a member substitution that skips any unresolvable fields or methods that are referenced within a method body. Using a relaxed * member substitution, methods containing optional types are supported. In the process, it is however possible that misconfigurations * of this component remain undiscovered. * * @return A relaxed member substitution. */ public static MemberSubstitution relaxed() { return new MemberSubstitution(false); } /** * Substitutes any interaction with a field or method that matches the given matcher. * * @param matcher The matcher to determine what access to byte code elements to substitute. * @return A specification that allows to determine how to substitute any interaction with byte code elements that match the supplied matcher. */ public WithoutSpecification element(ElementMatcher matcher) { return new WithoutSpecification.ForMatchedByteCodeElement(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); } /** * Substitutes any field access that matches the given matcher. * * @param matcher The matcher to determine what fields to substitute. * @return A specification that allows to determine how to substitute any field access that match the supplied matcher. */ public WithoutSpecification.ForMatchedField field(ElementMatcher matcher) { return new WithoutSpecification.ForMatchedField(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); } /** * Substitutes any method invocation that matches the given matcher. * * @param matcher The matcher to determine what methods to substitute. * @return A specification that allows to determine how to substitute any method invocations that match the supplied matcher. */ public WithoutSpecification.ForMatchedMethod method(ElementMatcher matcher) { return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); } /** * Substitutes any constructor invocation that matches the given matcher. * * @param matcher The matcher to determine what constructors to substitute. * @return A specification that allows to determine how to substitute any constructor invocations that match the supplied matcher. */ public WithoutSpecification constructor(ElementMatcher matcher) { return invokable(isConstructor().and(matcher)); } /** * Substitutes any method or constructor invocation that matches the given matcher. * * @param matcher The matcher to determine what method or constructors to substitute. * @return A specification that allows to determine how to substitute any constructor invocations that match the supplied matcher. */ public WithoutSpecification invokable(ElementMatcher matcher) { return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); } /** * Specifies the use of a specific method graph compiler for the resolution of virtual methods. * * @param methodGraphCompiler The method graph compiler to use. * @return A new member substitution that is equal to this but uses the specified method graph compiler. */ public MemberSubstitution with(MethodGraph.Compiler methodGraphCompiler) { return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, replacementFactory); } /** * Specifies a type pool resolver to be used for locating members. * * @param typePoolResolver The type pool resolver to use. * @return A new instance of this member substitution that uses the supplied type pool resolver. */ public MemberSubstitution with(TypePoolResolver typePoolResolver) { return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, replacementFactory); } /** * Applies this member substitution to any method that matches the supplied matcher. * * @param matcher The matcher to determine this substitutions application. * @return An ASM visitor wrapper that applies all specified substitutions for any matched method. */ 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) { typePool = typePoolResolver.resolve(instrumentedType, instrumentedMethod, typePool); return new SubstitutingMethodVisitor(methodVisitor, instrumentedType, instrumentedMethod, methodGraphCompiler, strict, replacementFactory.make(instrumentedType, instrumentedMethod, typePool), implementationContext, typePool, implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V11)); } /** * A member substitution that lacks a specification for how to substitute the matched members references within a method body. */ @HashCodeAndEqualsPlugin.Enhance public abstract static class WithoutSpecification { /** * The method graph compiler to use. */ protected final MethodGraph.Compiler methodGraphCompiler; /** * The type pool resolver to use. */ protected final TypePoolResolver typePoolResolver; /** * {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. */ protected final boolean strict; /** * The replacement factory to use for creating substitutions. */ protected final Replacement.Factory replacementFactory; /** * Creates a new member substitution that requires a specification for how to perform a substitution. * * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacementFactory The replacement factory to use for creating substitutions. */ protected WithoutSpecification(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory) { this.methodGraphCompiler = methodGraphCompiler; this.typePoolResolver = typePoolResolver; this.strict = strict; this.replacementFactory = replacementFactory; } /** * Subs any interaction with a matched byte code element. Any value read from the element will be replaced with the stubbed * value's default, i.e. {@code null} for reference types and the specific {@code 0} value for primitive types. Any written * value will simply be discarded. * * @return A member substitution that stubs any interaction with a matched byte code element. */ public MemberSubstitution stub() { return replaceWith(Substitution.Stubbing.INSTANCE); } /** *

* Replaces any interaction with a matched byte code element by an interaction with the specified field. If a field * is replacing a method or constructor invocation, it is treated as if it was a field getter or setter respectively. *

*

* A replacement can only be applied if the field is compatible to the original byte code element, i.e. consumes an * instance of the declaring type if it is not {@code static} as an argument and consumes or produces an instance of * the field's type. *

* * @param field The field to access instead of interacting with any of the matched byte code elements. * @return A member substitution that replaces any matched byte code element with an access of the specified field. */ public MemberSubstitution replaceWith(Field field) { return replaceWith(new FieldDescription.ForLoadedField(field)); } /** *

* Replaces any interaction with a matched byte code element by an interaction with the specified field. If a field * is replacing a method or constructor invocation, it is treated as if it was a field getter or setter respectively. *

*

* A replacement can only be applied if the field is compatible to the original byte code element, i.e. consumes an * instance of the declaring type if it is not {@code static} as an argument and consumes or produces an instance of * the field's type. *

* * @param fieldDescription The field to access instead of interacting with any of the matched byte code elements. * @return A member substitution that replaces any matched byte code element with an access of the specified field. */ public MemberSubstitution replaceWith(FieldDescription fieldDescription) { return replaceWith(new Substitution.ForFieldAccess.OfGivenField(fieldDescription)); } /** * Replaces any interaction with a matched byte code element with a non-static field access on the first * parameter of the matched element. When matching a non-static field access or method invocation, the * substituted field is located on the same receiver type as the original access. For static access, the * first argument is used as a receiver. * * @param matcher A matcher for locating a field on the original interaction's receiver type. * @return A member substitution that replaces any matched byte code element with an access of the matched field. */ public MemberSubstitution replaceWithField(ElementMatcher matcher) { return replaceWith(new Substitution.ForFieldAccess.OfMatchedField(matcher)); } /** *

* Replaces any interaction with a matched byte code element by an invocation of the specified method. If a method * is replacing a field access, it is treated as if it was replacing an invocation of the field's getter or setter respectively. *

*

* A replacement can only be applied if the method is compatible to the original byte code element, i.e. consumes compatible * arguments and returns a compatible value. If the method is not {@code static}, it is treated as if {@code this} was an implicit * first argument. *

* * @param method The method to invoke instead of interacting with any of the matched byte code elements. * @return A member substitution that replaces any matched byte code element with an invocation of the specified method. */ public MemberSubstitution replaceWith(Method method) { return replaceWith(new MethodDescription.ForLoadedMethod(method)); } /** *

* Replaces any interaction with a matched byte code element by an invocation of the specified method. If a method * is replacing a field access, it is treated as if it was replacing an invocation of the field's getter or setter respectively. *

*

* A replacement can only be applied if the method is compatible to the original byte code element, i.e. consumes compatible * arguments and returns a compatible value. If the method is not {@code static}, it is treated as if {@code this} was an implicit * first argument. *

*

* Important: It is not allowed to specify a constructor or the static type initializer as a replacement. *

* * @param methodDescription The method to invoke instead of interacting with any of the matched byte code elements. * @return A member substitution that replaces any matched byte code element with an invocation of the specified method. */ public MemberSubstitution replaceWith(MethodDescription methodDescription) { if (!methodDescription.isMethod()) { throw new IllegalArgumentException("Cannot use " + methodDescription + " as a replacement"); } return replaceWith(new Substitution.ForMethodInvocation.OfGivenMethod(methodDescription)); } /** * Replaces any interaction with a matched byte code element with a non-static method access on the first * parameter of the matched element. When matching a non-static field access or method invocation, the * substituted method is located on the same receiver type as the original access. For static access, the * first argument is used as a receiver. * * @param matcher A matcher for locating a method on the original interaction's receiver type. * @return A member substitution that replaces any matched byte code element with an access of the matched method. */ public MemberSubstitution replaceWithMethod(ElementMatcher matcher) { return replaceWithMethod(matcher, methodGraphCompiler); } /** * Replaces any interaction with a matched byte code element with a non-static method access on the first * parameter of the matched element. When matching a non-static field access or method invocation, the * substituted method is located on the same receiver type as the original access. For static access, the * first argument is used as a receiver. * * @param matcher A matcher for locating a method on the original interaction's receiver type. * @param methodGraphCompiler The method graph compiler to use for locating a method. * @return A member substitution that replaces any matched byte code element with an access of the matched method. */ public MemberSubstitution replaceWithMethod(ElementMatcher matcher, MethodGraph.Compiler methodGraphCompiler) { return replaceWith(new Substitution.ForMethodInvocation.OfMatchedMethod(matcher, methodGraphCompiler)); } /** * Replaces any interaction with a matched byte code element with an invocation of the instrumented * method. This can cause an infinite recursive call if the arguments to the method are not altered. * * @return A member substitution that replaces any matched byte code element with an invocation of the * instrumented method. */ public MemberSubstitution replaceWithInstrumentedMethod() { return replaceWith(Substitution.ForMethodInvocation.OfInstrumentedMethod.INSTANCE); } /** * Replaces the matched byte code elements with a chain of substitutions that can operate on the same values as the substituted element. This is a * shortcut for creating a substitution chain with a default assigner. * * @param step The steps to apply for a substitution. * @return A member substitution that replaces any matched byte code element with the provided substitution chain. */ public MemberSubstitution replaceWithChain(Substitution.Chain.Step.Factory... step) { return replaceWithChain(Arrays.asList(step)); } /** * Replaces the matched byte code elements with a chain of substitutions that can operate on the same values as the substituted element. This is a * shortcut for creating a substitution chain with a default assigner. * * @param steps The steps to apply for a substitution. * @return A member substitution that replaces any matched byte code element with the provided substitution chain. */ public MemberSubstitution replaceWithChain(List steps) { return replaceWith(Substitution.Chain.withDefaultAssigner().executing(steps)); } /** * Replaces any interaction with the supplied substitution. * * @param factory The substitution factory to use for creating the applied substitution. * @return A member substitution that replaces any matched byte code element with the supplied substitution. */ public abstract MemberSubstitution replaceWith(Substitution.Factory factory); /** * Describes a member substitution that requires a specification for how to replace a byte code element. */ @HashCodeAndEqualsPlugin.Enhance protected static class ForMatchedByteCodeElement extends WithoutSpecification { /** * A matcher for any byte code elements that should be substituted. */ private final ElementMatcher matcher; /** * Creates a new member substitution for a matched byte code element that requires a specification for how to perform a substitution. * * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any byte code elements that should be substituted. */ protected ForMatchedByteCodeElement(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory, ElementMatcher matcher) { super(methodGraphCompiler, typePoolResolver, strict, replacementFactory); this.matcher = matcher; } /** * {@inheritDoc} */ public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) { return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, new Replacement.Factory.Compound( this.replacementFactory, Replacement.ForElementMatchers.Factory.of(matcher, substitutionFactory))); } } /** * Describes a member substitution that requires a specification for how to replace a field. */ @HashCodeAndEqualsPlugin.Enhance public static class ForMatchedField extends WithoutSpecification { /** * A matcher for any field that should be substituted. */ private final ElementMatcher matcher; /** * {@code true} if read access to a field should be substituted. */ private final boolean matchRead; /** * {@code true} if write access to a field should be substituted. */ private final boolean matchWrite; /** * Creates a new member substitution for a matched field that requires a specification for how to perform a substitution. * * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any field that should be substituted. */ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory, ElementMatcher matcher) { this(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, true, true); } /** * Creates a new member substitution for a matched field that requires a specification for how to perform a substitution. * * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any field that should be substituted. * @param matchRead {@code true} if read access to a field should be substituted. * @param matchWrite {@code true} if write access to a field should be substituted. */ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory, ElementMatcher matcher, boolean matchRead, boolean matchWrite) { super(methodGraphCompiler, typePoolResolver, strict, replacementFactory); this.matcher = matcher; this.matchRead = matchRead; this.matchWrite = matchWrite; } /** * When invoked, only read access of the previously matched field is substituted. * * @return This instance with the limitation that only read access to the matched field is substituted. */ public WithoutSpecification onRead() { return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, true, false); } /** * When invoked, only write access of the previously matched field is substituted. * * @return This instance with the limitation that only write access to the matched field is substituted. */ public WithoutSpecification onWrite() { return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, false, true); } /** * {@inheritDoc} */ public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) { return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, new Replacement.Factory.Compound( this.replacementFactory, Replacement.ForElementMatchers.Factory.ofField(matcher, matchRead, matchWrite, substitutionFactory))); } } /** * Describes a member substitution that requires a specification for how to replace a method or constructor. */ @HashCodeAndEqualsPlugin.Enhance public static class ForMatchedMethod extends WithoutSpecification { /** * A matcher for any method or constructor that should be substituted. */ private final ElementMatcher matcher; /** * {@code true} if this specification includes virtual invocations. */ private final boolean includeVirtualCalls; /** * {@code true} if this specification includes {@code super} invocations. */ private final boolean includeSuperCalls; /** * Creates a new member substitution for a matched method that requires a specification for how to perform a substitution. * * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any method or constructor that should be substituted. */ protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory, ElementMatcher matcher) { this(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, true, true); } /** * Creates a new member substitution for a matched method that requires a specification for how to perform a substitution. * * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any method or constructor that should be substituted. * @param includeVirtualCalls {@code true} if this specification includes virtual invocations. * @param includeSuperCalls {@code true} if this specification includes {@code super} invocations. */ protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory, ElementMatcher matcher, boolean includeVirtualCalls, boolean includeSuperCalls) { super(methodGraphCompiler, typePoolResolver, strict, replacementFactory); this.matcher = matcher; this.includeVirtualCalls = includeVirtualCalls; this.includeSuperCalls = includeSuperCalls; } /** * Limits the substituted method calls to method calls that invoke a method virtually (as opposed to a {@code super} invocation). * * @return This specification where only virtual methods are matched if they are not invoked as a virtual call. */ public WithoutSpecification onVirtualCall() { return new ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, replacementFactory, isVirtual().and(matcher), true, false); } /** * Limits the substituted method calls to method calls that invoke a method as a {@code super} call. * * @return This specification where only virtual methods are matched if they are not invoked as a super call. */ public WithoutSpecification onSuperCall() { return new ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, replacementFactory, isVirtual().and(matcher), false, true); } /** * {@inheritDoc} */ public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) { return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, new Replacement.Factory.Compound( this.replacementFactory, Replacement.ForElementMatchers.Factory.ofMethod(matcher, includeVirtualCalls, includeSuperCalls, substitutionFactory))); } } } /** * A type pool resolver is responsible for resolving a {@link TypePool} for locating substituted members. */ public interface TypePoolResolver { /** * Resolves a type pool to use for locating substituted members. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param typePool The type pool implicit to the instrumentation. * @return The type pool to use. */ TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool); /** * Returns the implicit type pool. */ enum OfImplicitPool implements TypePoolResolver { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return typePool; } } /** * A type pool resolver that returns a specific type pool. */ @HashCodeAndEqualsPlugin.Enhance class ForExplicitPool implements TypePoolResolver { /** * The type pool to return. */ private final TypePool typePool; /** * Creates a resolver for an explicit type pool. * * @param typePool The type pool to return. */ public ForExplicitPool(TypePool typePool) { this.typePool = typePool; } /** * {@inheritDoc} */ public TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return this.typePool; } } /** * A type pool resolver that resolves the implicit pool but additionally checks another class file locator. */ @HashCodeAndEqualsPlugin.Enhance class ForClassFileLocator implements TypePoolResolver { /** * The class file locator to use. */ private final ClassFileLocator classFileLocator; /** * The reader mode to apply. */ private final TypePool.Default.ReaderMode readerMode; /** * Creates a new type pool resolver for a class file locator as a supplement of the implicit type pool. * * @param classFileLocator The class file locator to use. */ public ForClassFileLocator(ClassFileLocator classFileLocator) { this(classFileLocator, TypePool.Default.ReaderMode.FAST); } /** * Creates a new type pool resolver for a class file locator as a supplement of the implicit type pool. * * @param classFileLocator The class file locator to use. * @param readerMode The reader mode to apply. */ public ForClassFileLocator(ClassFileLocator classFileLocator, TypePool.Default.ReaderMode readerMode) { this.classFileLocator = classFileLocator; this.readerMode = readerMode; } /** * Creates a new type pool resolver that supplements the supplied class loader to the implicit type pool. * * @param classLoader The class loader to use as a supplement which can be {@code null} to represent the bootstrap loader. * @return An appropriate type pool resolver. */ public static TypePoolResolver of(@MaybeNull ClassLoader classLoader) { return new ForClassFileLocator(ClassFileLocator.ForClassLoader.of(classLoader)); } /** * {@inheritDoc} */ public TypePool resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return new TypePool.Default(new TypePool.CacheProvider.Simple(), classFileLocator, readerMode, typePool); } } } /** * A substitution replaces or enhances an interaction with a field or method within an instrumented method. */ public interface Substitution { /** * Resolves this substitution into a stack manipulation. * * @param targetType The target type on which a member is accessed. * @param target The target field, method or constructor that is substituted, * @param parameters All parameters that serve as input to this access. * @param result The result that is expected from the interaction or {@code void} if no result is expected. * @param freeOffset The first free offset of the local variable array that can be used for storing values. * @return A stack manipulation that represents the access. */ StackManipulation resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset); /** * A factory for creating a substitution for an instrumented method. */ interface Factory { /** * Creates a substitution for an instrumented method. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param typePool The type pool being used. * @return The substitution to apply within the instrumented method. */ Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool); } /** * A substitution that drops any field or method access and returns the expected return type's default value, i.e {@code null} or zero for primitive types. */ enum Stubbing implements Substitution, Factory { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return this; } /** * {@inheritDoc} */ public StackManipulation resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset) { List stackManipulations = new ArrayList(parameters.size()); for (int index = parameters.size() - 1; index >= 0; index--) { stackManipulations.add(Removal.of(parameters.get(index))); } return new StackManipulation.Compound(CompoundList.of(stackManipulations, DefaultValue.of(result.asErasure()))); } } /** * A substitution with a field access. */ @HashCodeAndEqualsPlugin.Enhance class ForFieldAccess implements Substitution { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * A resolver to locate the field to access. */ private final FieldResolver fieldResolver; /** * Creates a new substitution with a field access. * * @param instrumentedType The instrumented type. * @param fieldResolver A resolver to locate the field to access. */ public ForFieldAccess(TypeDescription instrumentedType, FieldResolver fieldResolver) { this.instrumentedType = instrumentedType; this.fieldResolver = fieldResolver; } /** * {@inheritDoc} */ @SuppressFBWarnings(value = "NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE", justification = "Assuming declaring type for type member.") public StackManipulation resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset) { FieldDescription fieldDescription = fieldResolver.resolve(targetType, target, parameters, result); if (!fieldDescription.isAccessibleTo(instrumentedType)) { throw new IllegalStateException(instrumentedType + " cannot access " + fieldDescription); } else if (result.represents(void.class)) { if (parameters.size() != (fieldDescription.isStatic() ? 1 : 2)) { throw new IllegalStateException("Cannot set " + fieldDescription + " with " + parameters); } else if (!fieldDescription.isStatic() && !parameters.get(0).asErasure().isAssignableTo(fieldDescription.getDeclaringType().asErasure())) { throw new IllegalStateException("Cannot set " + fieldDescription + " on " + parameters.get(0)); } else if (!parameters.get(fieldDescription.isStatic() ? 0 : 1).asErasure().isAssignableTo(fieldDescription.getType().asErasure())) { throw new IllegalStateException("Cannot set " + fieldDescription + " to " + parameters.get(fieldDescription.isStatic() ? 0 : 1)); } return FieldAccess.forField(fieldDescription).write(); } else { if (parameters.size() != (fieldDescription.isStatic() ? 0 : 1)) { throw new IllegalStateException("Cannot set " + fieldDescription + " with " + parameters); } else if (!fieldDescription.isStatic() && !parameters.get(0).asErasure().isAssignableTo(fieldDescription.getDeclaringType().asErasure())) { throw new IllegalStateException("Cannot get " + fieldDescription + " on " + parameters.get(0)); } else if (!fieldDescription.getType().asErasure().isAssignableTo(result.asErasure())) { throw new IllegalStateException("Cannot get " + fieldDescription + " as " + result); } return FieldAccess.forField(fieldDescription).read(); } } /** * A method resolver for locating a field for a substitute. */ public interface FieldResolver { /** * Resolves the field to substitute with. * * @param targetType The target type on which a member is accessed. * @param target The target field, method or constructor that is substituted, * @param parameters All parameters that serve as input to this access. * @param result The result that is expected from the interaction or {@code void} if no result is expected. * @return The field to substitute with. */ FieldDescription resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result); /** * A simple field resolver that returns a specific field. */ @HashCodeAndEqualsPlugin.Enhance class Simple implements FieldResolver { /** * The field to access. */ private final FieldDescription fieldDescription; /** * Creates a simple field resolver. * * @param fieldDescription The field to access. */ public Simple(FieldDescription fieldDescription) { this.fieldDescription = fieldDescription; } /** * {@inheritDoc} */ public FieldDescription resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result) { return fieldDescription; } } /** * A field matcher that resolves a non-static field on the first parameter type of the substituted member usage. */ @HashCodeAndEqualsPlugin.Enhance class ForElementMatcher implements FieldResolver { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The matcher to use for locating the field to substitute with. */ private final ElementMatcher matcher; /** * Creates a new field resolver that locates a field on the receiver type using a matcher. * * @param instrumentedType The instrumented type. * @param matcher The matcher to use for locating the field to substitute with. */ protected ForElementMatcher(TypeDescription instrumentedType, ElementMatcher matcher) { this.instrumentedType = instrumentedType; this.matcher = matcher; } /** * {@inheritDoc} */ public FieldDescription resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result) { if (parameters.isEmpty()) { throw new IllegalStateException("Cannot substitute parameterless instruction with " + target); } else if (parameters.get(0).isPrimitive() || parameters.get(0).isArray()) { throw new IllegalStateException("Cannot access field on primitive or array type for " + target); } TypeDefinition current = parameters.get(0).accept(new TypeDescription.Generic.Visitor.Substitutor.ForReplacement(instrumentedType)); do { FieldList fields = current.getDeclaredFields().filter(not(isStatic()).and(isVisibleTo(instrumentedType)).and(matcher)); if (fields.size() == 1) { return fields.getOnly(); } else if (fields.size() > 1) { throw new IllegalStateException("Ambiguous field location of " + fields); } current = current.getSuperClass(); } while (current != null); throw new IllegalStateException("Cannot locate field matching " + matcher + " on " + targetType); } } } /** * A factory for a substitution that substitutes with a given field. */ @HashCodeAndEqualsPlugin.Enhance public static class OfGivenField implements Factory { /** * The field to substitute with. */ private final FieldDescription fieldDescription; /** * Creates a new factory that substitues with a given field. * * @param fieldDescription The field to substitute with. */ public OfGivenField(FieldDescription fieldDescription) { this.fieldDescription = fieldDescription; } /** * {@inheritDoc} */ public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return new ForFieldAccess(instrumentedType, new FieldResolver.Simple(fieldDescription)); } } /** * A factory for a substitution that locates a field on the receiver type using a matcher. */ @HashCodeAndEqualsPlugin.Enhance public static class OfMatchedField implements Factory { /** * The matcher to apply. */ private final ElementMatcher matcher; /** * Creates a new substitution factory that locates a field by applying a matcher on the receiver type. * * @param matcher The matcher to apply. */ public OfMatchedField(ElementMatcher matcher) { this.matcher = matcher; } /** * {@inheritDoc} */ public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return new ForFieldAccess(instrumentedType, new FieldResolver.ForElementMatcher(instrumentedType, matcher)); } } } /** * A substitution with a method invocation. */ @HashCodeAndEqualsPlugin.Enhance class ForMethodInvocation implements Substitution { /** * The index of the this reference within a non-static method. */ private static final int THIS_REFERENCE = 0; /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The method resolver to use. */ private final MethodResolver methodResolver; /** * Creates a new method-resolving substitution. * * @param instrumentedType The instrumented type. * @param methodResolver The method resolver to use. */ public ForMethodInvocation(TypeDescription instrumentedType, MethodResolver methodResolver) { this.instrumentedType = instrumentedType; this.methodResolver = methodResolver; } /** * {@inheritDoc} */ public StackManipulation resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset) { MethodDescription methodDescription = methodResolver.resolve(targetType, target, parameters, result); if (!methodDescription.isAccessibleTo(instrumentedType)) { throw new IllegalStateException(instrumentedType + " cannot access " + methodDescription); } TypeList.Generic mapped = methodDescription.isStatic() ? methodDescription.getParameters().asTypeList() : new TypeList.Generic.Explicit(CompoundList.of(methodDescription.getDeclaringType(), methodDescription.getParameters().asTypeList())); if (!methodDescription.getReturnType().asErasure().isAssignableTo(result.asErasure())) { throw new IllegalStateException("Cannot assign return value of " + methodDescription + " to " + result); } else if (mapped.size() != parameters.size()) { throw new IllegalStateException("Cannot invoke " + methodDescription + " on " + parameters.size() + " parameters"); } for (int index = 0; index < mapped.size(); index++) { if (!parameters.get(index).asErasure().isAssignableTo(mapped.get(index).asErasure())) { throw new IllegalStateException("Cannot invoke " + methodDescription + " on parameter " + index + " of type " + parameters.get(index)); } } return methodDescription.isVirtual() ? MethodInvocation.invoke(methodDescription).virtual(mapped.get(THIS_REFERENCE).asErasure()) : MethodInvocation.invoke(methodDescription); } /** * A method resolver for locating a method for a substitute. */ public interface MethodResolver { /** * Resolves the method to substitute with. * * @param targetType The target type on which a member is accessed. * @param target The target field, method or constructor that is substituted, * @param parameters All parameters that serve as input to this access. * @param result The result that is expected from the interaction or {@code void} if no result is expected. * @return The field to substitute with. */ MethodDescription resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result); /** * A simple method resolver that returns a given method. */ @HashCodeAndEqualsPlugin.Enhance class Simple implements MethodResolver { /** * The method to substitute with. */ private final MethodDescription methodDescription; /** * Creates a new simple method resolver. * * @param methodDescription The method to substitute with. */ public Simple(MethodDescription methodDescription) { this.methodDescription = methodDescription; } /** * {@inheritDoc} */ public MethodDescription resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result) { return methodDescription; } } /** * A method resolver that locates a non-static method by locating it from the receiver type. */ @HashCodeAndEqualsPlugin.Enhance class Matching implements MethodResolver { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The method graph compiler to use. */ private final MethodGraph.Compiler methodGraphCompiler; /** * The matcher to use for locating the method to substitute with. */ private final ElementMatcher matcher; /** * Creates a new matching method resolver. * * @param instrumentedType The instrumented type. * @param methodGraphCompiler The method graph compiler to use. * @param matcher The matcher to use for locating the method to substitute with. */ public Matching(TypeDescription instrumentedType, MethodGraph.Compiler methodGraphCompiler, ElementMatcher matcher) { this.instrumentedType = instrumentedType; this.methodGraphCompiler = methodGraphCompiler; this.matcher = matcher; } /** * {@inheritDoc} */ public MethodDescription resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result) { if (parameters.isEmpty()) { throw new IllegalStateException("Cannot substitute parameterless instruction with " + target); } else if (parameters.get(0).isPrimitive() || parameters.get(0).isArray()) { throw new IllegalStateException("Cannot invoke method on primitive or array type for " + target); } TypeDefinition typeDefinition = parameters.get(0).accept(new TypeDescription.Generic.Visitor.Substitutor.ForReplacement(instrumentedType)); List candidates = CompoundList.of(methodGraphCompiler.compile(typeDefinition, instrumentedType) .listNodes() .asMethodList() .filter(matcher), typeDefinition.getDeclaredMethods().filter(isPrivate().and(isVisibleTo(instrumentedType)).and(matcher))); if (candidates.size() == 1) { return candidates.get(0); } else { throw new IllegalStateException("Not exactly one method that matches " + matcher + ": " + candidates); } } } } /** * A factory for a substitution that invokes the instrumented method. */ enum OfInstrumentedMethod implements Factory { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return new ForMethodInvocation(instrumentedType, new MethodResolver.Simple(instrumentedMethod)); } } /** * A factory for a substitution that invokes a given method. */ @HashCodeAndEqualsPlugin.Enhance public static class OfGivenMethod implements Factory { /** * The method to invoke. */ private final MethodDescription methodDescription; /** * Creates a new factory for a substitution that invokes a given method. * * @param methodDescription The method to invoke. */ public OfGivenMethod(MethodDescription methodDescription) { this.methodDescription = methodDescription; } /** * {@inheritDoc} */ public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return new ForMethodInvocation(instrumentedType, new MethodResolver.Simple(methodDescription)); } } /** * A factory for a substitution that locates a method on the receiver type using a matcher. */ @HashCodeAndEqualsPlugin.Enhance public static class OfMatchedMethod implements Factory { /** * The matcher for locating the method to substitute with. */ private final ElementMatcher matcher; /** * The method graph compiler to use. */ private final MethodGraph.Compiler methodGraphCompiler; /** * Creates a factory for a substitution that locates a method on the receiver type. * * @param matcher The matcher for locating the method to substitute with. * @param methodGraphCompiler The method graph compiler to use. */ public OfMatchedMethod(ElementMatcher matcher, MethodGraph.Compiler methodGraphCompiler) { this.matcher = matcher; this.methodGraphCompiler = methodGraphCompiler; } /** * {@inheritDoc} */ public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return new ForMethodInvocation(instrumentedType, new MethodResolver.Matching(instrumentedType, methodGraphCompiler, matcher)); } } } /** * A substitution chain allows for chaining multiple substitution steps for a byte code element being replaced. */ @HashCodeAndEqualsPlugin.Enhance class Chain implements Substitution { /** * The assigner to use. */ private final Assigner assigner; /** * The typing of the assignment to use. */ private final Assigner.Typing typing; /** * The substitution steps to apply. */ private final List steps; /** * Creates a new substitution chain. * * @param assigner The assigner to use. * @param typing The typing of the assignment to use. * @param steps The substitution steps to apply. */ protected Chain(Assigner assigner, Assigner.Typing typing, List steps) { this.assigner = assigner; this.typing = typing; this.steps = steps; } /** * Creates a new substitution chain that uses a default assigner and static typing. * * @return A new substitution chain. */ public static Chain.Factory withDefaultAssigner() { return with(Assigner.DEFAULT, Assigner.Typing.STATIC); } /** * Creates a new substitution chain. * * @param assigner The assigner to use. * @param typing The typing of the assignment to use. * @return A new substitution chain. */ public static Chain.Factory with(Assigner assigner, Assigner.Typing typing) { return new Chain.Factory(assigner, typing, Collections.emptyList()); } /** * {@inheritDoc} */ public StackManipulation resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset) { List stackManipulations = new ArrayList(1 + parameters.size() + steps.size() * 2 + (result.represents(void.class) ? 0 : 2)); Map offsets = new HashMap(); for (int index = parameters.size() - 1; index >= 0; index--) { stackManipulations.add(MethodVariableAccess.of(parameters.get(index)).storeAt(freeOffset)); offsets.put(index, freeOffset); freeOffset += parameters.get(index).getStackSize().getSize(); } stackManipulations.add(DefaultValue.of(result)); TypeDescription.Generic current = result; for (Step step : steps) { Step.Resolution resulution = step.resolve(targetType, target, parameters, current, offsets, freeOffset); stackManipulations.add(resulution.getStackManipulation()); current = resulution.getResultType(); } stackManipulations.add(assigner.assign(current, result, typing)); return new StackManipulation.Compound(stackManipulations); } /** * Represents a step of a substitution chain. */ protected interface Step { /** * Resolves this step of a substitution chain. * * @param targetType The target result type of the substitution. * @param target The byte code element that is currently substituted. * @param parameters The parameters of the substituted element. * @param current The current type of the applied substitution that is the top element on the operand stack. * @param offsets The arguments of the substituted byte code element mapped to their local variable offsets. * @param freeOffset The first free offset in the local variable array. * @return A resolved substitution step for the supplied inputs. */ Resolution resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic current, Map offsets, int freeOffset); /** * A resolved substitution step. */ interface Resolution { /** * Returns the stack manipulation to apply the substitution. * * @return The stack manipulation to apply the substitution. */ StackManipulation getStackManipulation(); /** * Returns the resulting type of the substitution or {@code void} if no resulting value is applied. * * @return The resulting type of the substitution or {@code void} if no resulting value is applied. */ TypeDescription.Generic getResultType(); } /** * Resolves a substitution for an instrumented method. */ interface Factory { /** * Creates a substitution step for an instrumented method. * * @param assigner The assigner to use. * @param typing The typing to use. * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @return The substitution step to apply. */ Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod); } /** * A simple substitution step within a substitution chain. */ @HashCodeAndEqualsPlugin.Enhance class Simple implements Step, Resolution, Factory { /** * The stack manipulation to apply. */ private final StackManipulation stackManipulation; /** * The resulting type of applying the stack manipulation. */ private final TypeDescription.Generic resultType; /** * Creates a new simple substitution step. * * @param stackManipulation The stack manipulation to apply. * @param resultType The resulting type of applying the stack manipulation. */ public Simple(StackManipulation stackManipulation, TypeDescription.Generic resultType) { this.stackManipulation = stackManipulation; this.resultType = resultType; } /** * {@inheritDoc} */ public Step make(Assigner assigner, Assigner.Typing typing, TypeDescription instrumentedType, MethodDescription instrumentedMethod) { return this; } /** * {@inheritDoc} */ public Resolution resolve(TypeDescription targetType, ByteCodeElement target, TypeList.Generic parameters, TypeDescription.Generic current, Map offsets, int freeOffset) { return targetType.represents(void.class) ? this : new Simple(new StackManipulation.Compound(Removal.of(targetType), stackManipulation), resultType); } /** * {@inheritDoc} */ public StackManipulation getStackManipulation() { return stackManipulation; } /** * {@inheritDoc} */ public TypeDescription.Generic getResultType() { return resultType; } } } /** * A factory for creating a substitution chain. */ public static class Factory implements Substitution.Factory { /** * The assigner to use. */ private final Assigner assigner; /** * The typing of the assignment to use. */ private final Assigner.Typing typing; /** * The substitution steps to apply. */ private final List steps; /** * Creates a new factory for a substitution chain. * * @param assigner The assigner to use. * @param typing The typing of the assignment to use. * @param steps The substitution steps to apply. */ protected Factory(Assigner assigner, Assigner.Typing typing, List steps) { this.assigner = assigner; this.typing = typing; this.steps = steps; } /** * {@inheritDoc} */ public Substitution make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { if (steps.isEmpty()) { return Stubbing.INSTANCE; } List steps = new ArrayList(this.steps.size()); for (Step.Factory step : this.steps) { steps.add(step.make(assigner, typing, instrumentedType, instrumentedMethod)); } return new Chain(assigner, typing, steps); } /** * Appends the supplied steps to the substitution chain. * * @param step The steps to append. * @return A new substitution chain that is equal to this substitution chain but with the supplied steps appended. */ public Factory executing(Step.Factory... step) { return executing(Arrays.asList(step)); } /** * Appends the supplied steps to the substitution chain. * * @param steps The steps to append. * @return A new substitution chain that is equal to this substitution chain but with the supplied steps appended. */ public Factory executing(List steps) { return new Factory(assigner, typing, CompoundList.of(this.steps, steps)); } } } } /** * A replacement combines a {@link Substitution} and a way of choosing if this substitution should be applied for a discovered member. */ protected interface Replacement { /** * Binds this replacement for a field that was discovered. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param fieldDescription The field that was discovered. * @param writeAccess {@code true} if this field was written to. * @return A binding for the discovered field access. */ Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, FieldDescription.InDefinedShape fieldDescription, boolean writeAccess); /** * Binds this replacement for a field that was discovered. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param typeDescription The type on which the method was invoked. * @param methodDescription The method that was discovered. * @param invocationType The invocation type for this method. * @return A binding for the discovered method invocation. */ Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypeDescription typeDescription, MethodDescription methodDescription, InvocationType invocationType); /** * A binding for a replacement of a field or method access within another method. */ interface Binding { /** * Returns {@code true} if this binding is resolved. * * @return {@code true} if this binding is resolved. */ boolean isBound(); /** * Creates a stack manipulation that represents the substitution. This method can only be called for actually bound bindings. * * @param parameters The parameters that are accessible to the substitution target. * @param result The result that is expected from the substitution target or {@code void} if none is expected. * @param freeOffset The first offset that can be used for storing local variables. * @return A stack manipulation that represents the replacement. */ StackManipulation make(TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset); /** * An unresolved binding. */ enum Unresolved implements Binding { /** * The singleton instance. */ INSTANCE; /** * {@inheritDoc} */ public boolean isBound() { return false; } /** * {@inheritDoc} */ public StackManipulation make(TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset) { throw new IllegalStateException("Cannot resolve unresolved binding"); } } /** * A binding that was resolved for an actual substitution. */ @HashCodeAndEqualsPlugin.Enhance class Resolved implements Binding { /** * The type on which a field or method was accessed. */ private final TypeDescription targetType; /** * The field or method that was accessed. */ private final ByteCodeElement target; /** * The substitution to apply. */ private final Substitution substitution; /** * Creates a new resolved binding. * * @param targetType The type on which a field or method was accessed. * @param target The field or method that was accessed. * @param substitution The substitution to apply. */ protected Resolved(TypeDescription targetType, ByteCodeElement target, Substitution substitution) { this.targetType = targetType; this.target = target; this.substitution = substitution; } /** * {@inheritDoc} */ public boolean isBound() { return true; } /** * {@inheritDoc} */ public StackManipulation make(TypeList.Generic parameters, TypeDescription.Generic result, int freeOffset) { return substitution.resolve(targetType, target, parameters, result, freeOffset); } } } /** * A factory for creating a replacement for an instrumented method. */ interface Factory { /** * Creates a replacement for an instrumented method. * * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param typePool The type pool being used within the member substitution being applied. * @return A replacement to use within the supplied instrumented method. */ Replacement make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool); /** * A compound factory. */ @HashCodeAndEqualsPlugin.Enhance class Compound implements Factory { /** * A list of represented factories. */ private final List factories; /** * Creates a new compound factory. * * @param factory A list of represented factories. */ protected Compound(Factory... factory) { this(Arrays.asList(factory)); } /** * Creates a new compound factory. * * @param factories A list of represented factories. */ protected 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 Replacement make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { List replacements = new ArrayList(); for (Factory factory : factories) { replacements.add(factory.make(instrumentedType, instrumentedMethod, typePool)); } return new ForFirstBinding(replacements); } } } /** * Describes a method invocation type. */ enum InvocationType { /** * Desribes a virtual method invocation. */ VIRTUAL, /** * Describes a super method invocation. */ SUPER, /** * Describes any method invocation that is not virtual or a super method invocation. */ OTHER; /** * Resolves an invocation type. * * @param opcode The opcode that is used for invoking the method. * @param methodDescription The method that is being invoked. * @return The invokation type for the method given that opcode. */ protected static InvocationType of(int opcode, MethodDescription methodDescription) { switch (opcode) { case Opcodes.INVOKEVIRTUAL: case Opcodes.INVOKEINTERFACE: return InvocationType.VIRTUAL; case Opcodes.INVOKESPECIAL: return methodDescription.isVirtual() ? SUPER : OTHER; default: return OTHER; } } /** * Checks if this invokation type matches the specified inputs. * * @param includeVirtualCalls {@code true} if a virtual method should be matched. * @param includeSuperCalls {@code true} if a super method call should be matched. * @return {@code true} if this invocation type matches the specified parameters. */ protected boolean matches(boolean includeVirtualCalls, boolean includeSuperCalls) { switch (this) { case VIRTUAL: return includeVirtualCalls; case SUPER: return includeSuperCalls; default: return true; } } } /** * A non-operational replacement. */ enum NoOp implements Replacement, Factory { /** * The singelton instance. */ INSTANCE; /** * {@inheritDoc} */ public Replacement make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return this; } /** * {@inheritDoc} */ public Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, FieldDescription.InDefinedShape fieldDescription, boolean writeAccess) { return Binding.Unresolved.INSTANCE; } /** * {@inheritDoc} */ public Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypeDescription typeDescription, MethodDescription methodDescription, InvocationType invocationType) { return Binding.Unresolved.INSTANCE; } } /** * A replacement that substitutes a member based on a row of element matchers. */ @HashCodeAndEqualsPlugin.Enhance class ForElementMatchers implements Replacement { /** * The field matcher to consider when discovering fields. */ private final ElementMatcher fieldMatcher; /** * The method matcher to consider when discovering methods. */ private final ElementMatcher methodMatcher; /** * {@code true} if field reading access should be matched. */ private final boolean matchFieldRead; /** * {@code true} if field writing access should be matched. */ private final boolean matchFieldWrite; /** * {@code true} if virtual method calls should be matched. */ private final boolean includeVirtualCalls; /** * {@code true} if super method calls should be matched. */ private final boolean includeSuperCalls; /** * The substitution to trigger if a member is matched. */ private final Substitution substitution; /** * Creates a new replacement that triggers a substitution based on a row of matchers. * * @param fieldMatcher The field matcher to consider when discovering fields. * @param methodMatcher The method matcher to consider when discovering methods. * @param matchFieldRead {@code true} if field reading access should be matched. * @param matchFieldWrite {@code true} if field writing access should be matched. * @param includeVirtualCalls {@code true} if virtual method calls should be matched. * @param includeSuperCalls {@code true} if super method calls should be matched. * @param substitution The substitution to trigger if a member is matched. */ protected ForElementMatchers(ElementMatcher fieldMatcher, ElementMatcher methodMatcher, boolean matchFieldRead, boolean matchFieldWrite, boolean includeVirtualCalls, boolean includeSuperCalls, Substitution substitution) { this.fieldMatcher = fieldMatcher; this.methodMatcher = methodMatcher; this.matchFieldRead = matchFieldRead; this.matchFieldWrite = matchFieldWrite; this.includeVirtualCalls = includeVirtualCalls; this.includeSuperCalls = includeSuperCalls; this.substitution = substitution; } /** * {@inheritDoc} */ public Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, FieldDescription.InDefinedShape fieldDescription, boolean writeAccess) { return (writeAccess ? matchFieldWrite : matchFieldRead) && fieldMatcher.matches(fieldDescription) ? new Binding.Resolved(fieldDescription.getDeclaringType(), fieldDescription, substitution) : Binding.Unresolved.INSTANCE; } /** * {@inheritDoc} */ public Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypeDescription typeDescription, MethodDescription methodDescription, InvocationType invocationType) { return invocationType.matches(includeVirtualCalls, includeSuperCalls) && methodMatcher.matches(methodDescription) ? new Binding.Resolved(typeDescription, methodDescription, substitution) : Binding.Unresolved.INSTANCE; } /** * A factory for creating a replacement that chooses members based on a row of element matchers. */ @HashCodeAndEqualsPlugin.Enhance protected static class Factory implements Replacement.Factory { /** * The field matcher to consider when discovering fields. */ private final ElementMatcher fieldMatcher; /** * The method matcher to consider when discovering methods. */ private final ElementMatcher methodMatcher; /** * {@code true} if field reading access should be matched. */ private final boolean matchFieldRead; /** * {@code true} if field writing access should be matched. */ private final boolean matchFieldWrite; /** * {@code true} if virtual method calls should be matched. */ private final boolean includeVirtualCalls; /** * {@code true} if super method calls should be matched. */ private final boolean includeSuperCalls; /** * The substitution factory to create a substitution from. */ private final Substitution.Factory substitutionFactory; /** * Creates a new replacement that triggers a substitution based on a row of matchers. * * @param fieldMatcher The field matcher to consider when discovering fields. * @param methodMatcher The method matcher to consider when discovering methods. * @param matchFieldRead {@code true} if field reading access should be matched. * @param matchFieldWrite {@code true} if field writing access should be matched. * @param includeVirtualCalls {@code true} if virtual method calls should be matched. * @param includeSuperCalls {@code true} if super method calls should be matched. * @param substitutionFactory The substitution factory to create a substitution from. */ protected Factory(ElementMatcher fieldMatcher, ElementMatcher methodMatcher, boolean matchFieldRead, boolean matchFieldWrite, boolean includeVirtualCalls, boolean includeSuperCalls, Substitution.Factory substitutionFactory) { this.fieldMatcher = fieldMatcher; this.methodMatcher = methodMatcher; this.matchFieldRead = matchFieldRead; this.matchFieldWrite = matchFieldWrite; this.includeVirtualCalls = includeVirtualCalls; this.includeSuperCalls = includeSuperCalls; this.substitutionFactory = substitutionFactory; } /** * Creates a factory for applying a substitution on all matched byte code elements for all access types. * * @param matcher The matcher to apply. * @param factory The substitution factory to create a substitution from. * @return An appropriate replacement factory for the supplied matcher and substitution factory. */ protected static Replacement.Factory of(ElementMatcher matcher, Substitution.Factory factory) { return new Factory(matcher, matcher, true, true, true, true, factory); } /** * Creates a factory that only matches field access for given access types. * * @param matcher The matcher to identify fields for substitution. * @param matchFieldRead {@code true} if field read access should be matched. * @param matchFieldWrite {@code true} if field write access should be matched. * @param factory The substitution factory to apply for fields that match the specified criteria. * @return An appropriate replacement factory. */ protected static Replacement.Factory ofField(ElementMatcher matcher, boolean matchFieldRead, boolean matchFieldWrite, Substitution.Factory factory) { return new Factory(matcher, none(), matchFieldRead, matchFieldWrite, false, false, factory); } /** * Creates a factory that only matches method and constructor invocations for given invocation types. * * @param matcher The matcher to identify methods and constructors for substitution. * @param includeVirtualCalls {@code true} if virtual method calls should be matched. * @param includeSuperCalls {@code true} if super method calls should be matched. * @param factory The substitution factory to apply for methods and constructors that match the specified criteria. * @return An appropriate replacement factory. */ protected static Replacement.Factory ofMethod(ElementMatcher matcher, boolean includeVirtualCalls, boolean includeSuperCalls, Substitution.Factory factory) { return new Factory(none(), matcher, false, false, includeVirtualCalls, includeSuperCalls, factory); } /** * {@inheritDoc} */ public Replacement make(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypePool typePool) { return new ForElementMatchers(fieldMatcher, methodMatcher, matchFieldRead, matchFieldWrite, includeVirtualCalls, includeSuperCalls, substitutionFactory.make(instrumentedType, instrumentedMethod, typePool)); } } } /** * A replacement that only resolves the first matching replacement of a list of replacements. */ @HashCodeAndEqualsPlugin.Enhance class ForFirstBinding implements Replacement { /** * The list of replacements to consider. */ private final List replacements; /** * Creates a new replacement that triggers the first matching replacement, if any. * * @param replacements The list of replacements to consider. */ protected ForFirstBinding(List replacements) { this.replacements = replacements; } /** * {@inheritDoc} */ public Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, FieldDescription.InDefinedShape fieldDescription, boolean writeAccess) { for (Replacement replacement : replacements) { Binding binding = replacement.bind(instrumentedType, instrumentedMethod, fieldDescription, writeAccess); if (binding.isBound()) { return binding; } } return Binding.Unresolved.INSTANCE; } /** * {@inheritDoc} */ public Binding bind(TypeDescription instrumentedType, MethodDescription instrumentedMethod, TypeDescription typeDescription, MethodDescription methodDescription, InvocationType invocationType) { for (Replacement replacement : replacements) { Binding binding = replacement.bind(instrumentedType, instrumentedMethod, typeDescription, methodDescription, invocationType); if (binding.isBound()) { return binding; } } return Binding.Unresolved.INSTANCE; } } } /** * A method visitor that applies a substitution for matched methods. */ protected static class SubstitutingMethodVisitor extends LocalVariableAwareMethodVisitor { /** * The instrumented type. */ private final TypeDescription instrumentedType; /** * The instrumented method. */ private final MethodDescription instrumentedMethod; /** * The method graph compiler to use. */ private final MethodGraph.Compiler methodGraphCompiler; /** * {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. */ private final boolean strict; /** * The replacement to use for creating substitutions. */ private final Replacement replacement; /** * The implementation context to use. */ private final Implementation.Context implementationContext; /** * The type pool to use. */ private final TypePool typePool; /** * If {@code true}, virtual method calls might target private methods in accordance to the nest mate specification. */ private final boolean virtualPrivateCalls; /** * An additional buffer for the operand stack that is required. */ private int stackSizeBuffer; /** * The minimum amount of local variable array slots that are required to apply substitutions. */ private int localVariableExtension; /** * Creates a new substituting method visitor. * * @param methodVisitor The method visitor to delegate to. * @param instrumentedType The instrumented type. * @param instrumentedMethod The instrumented method. * @param methodGraphCompiler The method graph compiler to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. * @param replacement The replacement to use for creating substitutions. * @param implementationContext The implementation context to use. * @param typePool The type pool to use. * @param virtualPrivateCalls {@code true}, virtual method calls might target private methods in accordance to the nest mate specification. */ protected SubstitutingMethodVisitor(MethodVisitor methodVisitor, TypeDescription instrumentedType, MethodDescription instrumentedMethod, MethodGraph.Compiler methodGraphCompiler, boolean strict, Replacement replacement, Implementation.Context implementationContext, TypePool typePool, boolean virtualPrivateCalls) { super(methodVisitor, instrumentedMethod); this.instrumentedType = instrumentedType; this.instrumentedMethod = instrumentedMethod; this.methodGraphCompiler = methodGraphCompiler; this.strict = strict; this.replacement = replacement; this.implementationContext = implementationContext; this.typePool = typePool; this.virtualPrivateCalls = virtualPrivateCalls; stackSizeBuffer = 0; localVariableExtension = 0; } @Override public void visitFieldInsn(int opcode, String owner, String internalName, String descriptor) { TypePool.Resolution resolution = typePool.describe(owner.replace('/', '.')); if (resolution.isResolved()) { FieldList candidates = resolution.resolve().getDeclaredFields().filter(strict ? ElementMatchers.named(internalName).and(hasDescriptor(descriptor)) : ElementMatchers.failSafe(named(internalName).and(hasDescriptor(descriptor)))); if (!candidates.isEmpty()) { Replacement.Binding binding = replacement.bind(instrumentedType, instrumentedMethod, candidates.getOnly(), opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC); if (binding.isBound()) { TypeList.Generic parameters; TypeDescription.Generic result; switch (opcode) { case Opcodes.PUTFIELD: parameters = new TypeList.Generic.Explicit(candidates.getOnly().getDeclaringType(), candidates.getOnly().getType()); result = TypeDescription.Generic.VOID; break; case Opcodes.PUTSTATIC: parameters = new TypeList.Generic.Explicit(candidates.getOnly().getType()); result = TypeDescription.Generic.VOID; break; case Opcodes.GETFIELD: parameters = new TypeList.Generic.Explicit(candidates.getOnly().getDeclaringType()); result = candidates.getOnly().getType(); break; case Opcodes.GETSTATIC: parameters = new TypeList.Generic.Empty(); result = candidates.getOnly().getType(); break; default: throw new IllegalStateException("Unexpected opcode: " + opcode); } stackSizeBuffer = Math.max(stackSizeBuffer, binding.make(parameters, result, getFreeOffset()) .apply(new LocalVariableTracingMethodVisitor(mv), implementationContext) .getMaximalSize() - result.getStackSize().getSize()); return; } } else if (strict) { throw new IllegalStateException("Could not resolve " + owner.replace('/', '.') + "." + internalName + descriptor + " using " + typePool); } } else if (strict) { throw new IllegalStateException("Could not resolve " + owner.replace('/', '.') + " using " + typePool); } super.visitFieldInsn(opcode, owner, internalName, descriptor); } @Override public void visitMethodInsn(int opcode, String owner, String internalName, String descriptor, boolean isInterface) { TypePool.Resolution resolution = typePool.describe(owner.replace('/', '.')); if (resolution.isResolved()) { MethodList candidates; if (opcode == Opcodes.INVOKESPECIAL && internalName.equals(MethodDescription.CONSTRUCTOR_INTERNAL_NAME)) { candidates = resolution.resolve().getDeclaredMethods().filter(strict ? ElementMatchers.isConstructor().and(hasDescriptor(descriptor)) : ElementMatchers.failSafe(isConstructor().and(hasDescriptor(descriptor)))); } else if (opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL) { candidates = resolution.resolve().getDeclaredMethods().filter(strict ? ElementMatchers.named(internalName).and(hasDescriptor(descriptor)) : ElementMatchers.failSafe(named(internalName).and(hasDescriptor(descriptor)))); } else if (virtualPrivateCalls) { candidates = resolution.resolve().getDeclaredMethods().filter(strict ? ElementMatchers.isPrivate().and(not(isStatic())).and(named(internalName).and(hasDescriptor(descriptor))) : ElementMatchers.failSafe(isPrivate().and(not(isStatic())).and(named(internalName).and(hasDescriptor(descriptor))))); if (candidates.isEmpty()) { candidates = methodGraphCompiler.compile((TypeDefinition) resolution.resolve(), instrumentedType).listNodes().asMethodList().filter(strict ? ElementMatchers.named(internalName).and(hasDescriptor(descriptor)) : ElementMatchers.failSafe(named(internalName).and(hasDescriptor(descriptor)))); } } else { candidates = methodGraphCompiler.compile((TypeDefinition) resolution.resolve(), instrumentedType).listNodes().asMethodList().filter(strict ? ElementMatchers.named(internalName).and(hasDescriptor(descriptor)) : ElementMatchers.failSafe(named(internalName).and(hasDescriptor(descriptor)))); } if (!candidates.isEmpty()) { Replacement.Binding binding = replacement.bind(instrumentedType, instrumentedMethod, resolution.resolve(), candidates.getOnly(), Replacement.InvocationType.of(opcode, candidates.getOnly())); if (binding.isBound()) { stackSizeBuffer = Math.max(stackSizeBuffer, binding.make( candidates.getOnly().isStatic() || candidates.getOnly().isConstructor() ? candidates.getOnly().getParameters().asTypeList() : new TypeList.Generic.Explicit(CompoundList.of(resolution.resolve(), candidates.getOnly().getParameters().asTypeList())), candidates.getOnly().isConstructor() ? candidates.getOnly().getDeclaringType().asGenericType() : candidates.getOnly().getReturnType(), getFreeOffset()) .apply(new LocalVariableTracingMethodVisitor(mv), implementationContext).getMaximalSize() - (candidates.getOnly().isConstructor() ? StackSize.SINGLE : candidates.getOnly().getReturnType().getStackSize()).getSize()); if (candidates.getOnly().isConstructor()) { stackSizeBuffer = Math.max(stackSizeBuffer, new StackManipulation.Compound( Duplication.SINGLE.flipOver(TypeDescription.OBJECT), Removal.SINGLE, Removal.SINGLE, Duplication.SINGLE.flipOver(TypeDescription.OBJECT), Removal.SINGLE, Removal.SINGLE ).apply(mv, implementationContext).getMaximalSize() + StackSize.SINGLE.getSize()); } return; } } else if (strict) { throw new IllegalStateException("Could not resolve " + owner.replace('/', '.') + "." + internalName + descriptor + " using " + typePool); } } else if (strict) { throw new IllegalStateException("Could not resolve " + owner.replace('/', '.') + " using " + typePool); } super.visitMethodInsn(opcode, owner, internalName, descriptor, isInterface); } @Override public void visitMaxs(int stackSize, int localVariableLength) { super.visitMaxs(stackSize + stackSizeBuffer, Math.max(localVariableExtension, localVariableLength)); } /** * A method visitor that traces offsets of the local variable array being used. */ private class LocalVariableTracingMethodVisitor extends MethodVisitor { /** * Creates a new local variable tracing method visitor. * * @param methodVisitor The method visitor to delegate to. */ private LocalVariableTracingMethodVisitor(MethodVisitor methodVisitor) { super(OpenedClassReader.ASM_API, methodVisitor); } @Override @SuppressFBWarnings(value = "SF_SWITCH_NO_DEFAULT", justification = "No action required on default option.") public void visitVarInsn(int opcode, int offset) { switch (opcode) { case Opcodes.ISTORE: case Opcodes.FSTORE: case Opcodes.ASTORE: localVariableExtension = Math.max(localVariableExtension, offset + 1); break; case Opcodes.LSTORE: case Opcodes.DSTORE: localVariableExtension = Math.max(localVariableExtension, offset + 2); break; } super.visitVarInsn(opcode, offset); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy