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

com.ui4j.bytebuddy.instrumentation.FixedValue Maven / Gradle / Ivy

There is a newer version: 2.1.0
Show newest version
package com.ui4j.bytebuddy.instrumentation;

import com.ui4j.bytebuddy.instrumentation.method.MethodDescription;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.ByteCodeAppender;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.StackManipulation;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.Assigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.primitive.PrimitiveTypeAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.primitive.VoidAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.assign.reference.ReferenceTypeAwareAssigner;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.constant.*;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.FieldAccess;
import com.ui4j.bytebuddy.instrumentation.method.bytecode.stack.member.MethodReturn;
import com.ui4j.bytebuddy.instrumentation.type.InstrumentedType;
import com.ui4j.bytebuddy.instrumentation.type.TypeDescription;
import com.ui4j.bytebuddy.jar.asm.MethodVisitor;
import com.ui4j.bytebuddy.jar.asm.Opcodes;

import static com.ui4j.bytebuddy.matcher.ElementMatchers.named;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.isValidIdentifier;
import static com.ui4j.bytebuddy.utility.ByteBuddyCommons.nonNull;

/**
 * This instrumentation returns a fixed value for a method. Other than the
 * {@link com.ui4j.bytebuddy.instrumentation.StubMethod} instrumentation, this implementation allows
 * to determine a specific value which must be assignable to the returning value of any instrumented method. Otherwise,
 * an exception will be thrown.
 */
public abstract class FixedValue implements Instrumentation {

    /**
     * The assigner that is used for assigning the fixed value to a method's return type.
     */
    protected final Assigner assigner;

    /**
     * Determines if the runtime type of a fixed value should be considered for the assignment to a return type.
     */
    protected final boolean considerRuntimeType;

    /**
     * Creates a new fixed value instrumentation.
     *
     * @param assigner            The assigner to use for assigning the fixed value to the return type of the instrumented value.
     * @param considerRuntimeType If {@code true}, the runtime type of the given value will be considered for assigning
     *                            the return type.
     */
    protected FixedValue(Assigner assigner, boolean considerRuntimeType) {
        this.assigner = assigner;
        this.considerRuntimeType = considerRuntimeType;
    }

    /**
     * Creates a fixed value instrumentation that returns {@code null} as a fixed value. This value is inlined into
     * the method and does not create a field.
     *
     * @return An instrumentation that represents the {@code null} value.
     */
    public static Instrumentation nullValue() {
        return value(null);
    }

    /**
     * Creates a fixed value instrumentation that returns a fixed value. If the value can be inlined into the created
     * class, i.e. can be added to the constant pool of a class, no explicit type initialization will be required for
     * the created dynamic class. Otherwise, a static field will be created in the dynamic type which will be initialized
     * with the given value. The following Java types can be inlined:
     * 
    *
  • The {@link java.lang.String} type.
  • *
  • All primitive types and their wrapper type, i.e. {@link java.lang.Boolean}, {@link java.lang.Byte}, * {@link java.lang.Short}, {@link java.lang.Character}, {@link java.lang.Integer}, {@link java.lang.Long}, * {@link java.lang.Float} and {@link java.lang.Double}.
  • *
  • A {@code null} reference.
  • *
*

 

* If possible, the constant pool value is substituted by a byte code instruction that creates the value. (This is * possible for integer types and types that are presented by integers inside the JVM ({@code boolean}, {@code byte}, * {@code short}, {@code char}) and for the {@code null} value. Additionally, several common constants of * the {@code float}, {@code double} and {@code long} types can be represented by opcode constants. Note that the * Java 7 types {@code java.lang.invoke.MethodHandle} and {@code java.lang.invoke.MethodType} are are currently not * supported for constant pool storage. * * @param fixedValue The fixed value to be returned by methods that are instrumented by this instrumentation. * @return An instrumentation for the given {@code fixedValue}. */ public static AssignerConfigurable value(Object fixedValue) { if (fixedValue == null) { return new ForPoolValue(NullConstant.INSTANCE, new TypeDescription.ForLoadedType(Object.class), defaultAssigner(), true); } Class type = fixedValue.getClass(); if (type == String.class) { return new ForPoolValue(new TextConstant((String) fixedValue), new TypeDescription.ForLoadedType(String.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Boolean.class) { return new ForPoolValue(IntegerConstant.forValue((Boolean) fixedValue), new TypeDescription.ForLoadedType(boolean.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Byte.class) { return new ForPoolValue(IntegerConstant.forValue((Byte) fixedValue), new TypeDescription.ForLoadedType(byte.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Short.class) { return new ForPoolValue(IntegerConstant.forValue((Short) fixedValue), new TypeDescription.ForLoadedType(short.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Character.class) { return new ForPoolValue(IntegerConstant.forValue((Character) fixedValue), new TypeDescription.ForLoadedType(char.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Integer.class) { return new ForPoolValue(IntegerConstant.forValue((Integer) fixedValue), new TypeDescription.ForLoadedType(int.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Long.class) { return new ForPoolValue(LongConstant.forValue((Long) fixedValue), new TypeDescription.ForLoadedType(long.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Float.class) { return new ForPoolValue(FloatConstant.forValue((Float) fixedValue), new TypeDescription.ForLoadedType(float.class), defaultAssigner(), defaultConsiderRuntimeType()); } else if (type == Double.class) { return new ForPoolValue(DoubleConstant.forValue((Double) fixedValue), new TypeDescription.ForLoadedType(double.class), defaultAssigner(), defaultConsiderRuntimeType()); } else { return reference(fixedValue); } } /** * Other than {@link com.ui4j.bytebuddy.instrumentation.FixedValue#value(Object)}, this function * will create a fixed value instrumentation that will always defined a field in the instrumented class. As a result, * object identity will be preserved between the given {@code fixedValue} and the value that is returned by * instrumented methods. *

 

* As an exception, the {@code null} value is always presented by a constant value and is never stored in a static * field. * * @param fixedValue The fixed value to be returned by methods that are instrumented by this instrumentation. * @return An instrumentation for the given {@code fixedValue}. */ public static AssignerConfigurable reference(Object fixedValue) { if (fixedValue == null) { return new ForPoolValue(NullConstant.INSTANCE, new TypeDescription.ForLoadedType(Object.class), defaultAssigner(), true); } return new ForStaticField(fixedValue, defaultAssigner(), defaultConsiderRuntimeType()); } /** * Other than {@link com.ui4j.bytebuddy.instrumentation.FixedValue#value(Object)}, this function * will create a fixed value instrumentation that will always defined a field in the instrumented class. As a result, * object identity will be preserved between the given {@code fixedValue} and the value that is returned by * instrumented methods. The field name can be explicitly determined. *

 

* As an exception, the {@code null} value cannot be used for this instrumentation but will cause an exception. * * @param fixedValue The fixed value to be returned by methods that are instrumented by this instrumentation. * @param fieldName The name of the field for storing the fixed value. * @return An instrumentation for the given {@code fixedValue}. */ public static AssignerConfigurable reference(Object fixedValue, String fieldName) { if (fixedValue == null) { throw new IllegalArgumentException("The fixed value must not be null"); } return new ForStaticField(isValidIdentifier(fieldName), fixedValue, defaultAssigner(), defaultConsiderRuntimeType()); } /** * Returns the default assigner that is to be used if no other assigner was explicitly specified. * * @return The default assigner that is to be used if no other assigner was explicitly specified. */ private static Assigner defaultAssigner() { return new VoidAwareAssigner(new PrimitiveTypeAwareAssigner(ReferenceTypeAwareAssigner.INSTANCE)); } /** * Determines if the runtime type should be considered by the given assigner by default. * * @return {@code true} if the runtime type should be considered by the given assigner by default. */ private static boolean defaultConsiderRuntimeType() { return false; } /** * Blueprint method that for applying the actual instrumentation. * * @param methodVisitor The method visitor to which the instrumentation is applied to. * @param instrumentationContext The instrumentation context for the given instrumentation. * @param instrumentedMethod The instrumented method that is target of the instrumentation. * @param fixedValueType A description of the type of the fixed value that is loaded by the * {@code valueLoadingInstruction}. * @param valueLoadingInstruction A stack manipulation that represents the loading of the fixed value onto the * operand stack. * @return A representation of the stack and variable array sized that are required for this instrumentation. */ protected ByteCodeAppender.Size apply(MethodVisitor methodVisitor, Context instrumentationContext, MethodDescription instrumentedMethod, TypeDescription fixedValueType, StackManipulation valueLoadingInstruction) { StackManipulation assignment = assigner.assign(fixedValueType, instrumentedMethod.getReturnType(), considerRuntimeType); if (!assignment.isValid()) { throw new IllegalArgumentException("Cannot return value of type " + fixedValueType + " for " + instrumentedMethod); } StackManipulation.Size stackSize = new StackManipulation.Compound( valueLoadingInstruction, assignment, MethodReturn.returning(instrumentedMethod.getReturnType()) ).apply(methodVisitor, instrumentationContext); return new ByteCodeAppender.Size(stackSize.getMaximalSize(), instrumentedMethod.getStackSize()); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && considerRuntimeType == ((FixedValue) other).considerRuntimeType && assigner.equals(((FixedValue) other).assigner); } @Override public int hashCode() { return 31 * assigner.hashCode() + (considerRuntimeType ? 1 : 0); } /** * Represents a fixed value instrumentation that is using a default assigner for attempting to assign * the fixed value to the return type of the instrumented method. */ public static interface AssignerConfigurable extends Instrumentation { /** * Defines an explicit assigner to this fixed value instrumentation. * * @param assigner The assigner to use for assigning the fixed value to the return type of the * instrumented value. * @param considerRuntimeType If {@code true}, the runtime type of the given value will be considered for * assigning the return type. * @return A fixed value instrumentation that makes use of the given assigner. */ Instrumentation withAssigner(Assigner assigner, boolean considerRuntimeType); } /** * A fixed value instrumentation that represents its fixed value as a value that is written to the instrumented * class's constant pool. */ protected static class ForPoolValue extends FixedValue implements AssignerConfigurable, ByteCodeAppender { /** * The stack manipulation which is responsible for loading the fixed value onto the operand stack. */ private final StackManipulation valueLoadInstruction; /** * The type of the fixed value. */ private final TypeDescription loadedType; /** * Creates a new constant pool fixed value instrumentation. * * @param valueLoadInstruction The instruction that is responsible for loading the constant pool value onto the * operand stack. * @param loadedType A type description representing the loaded type. * @param assigner The assigner to use for assigning the fixed value to the return type of the * instrumented value. * @param considerRuntimeType If {@code true}, the runtime type of the given value will be considered for * assigning the return type. */ private ForPoolValue(StackManipulation valueLoadInstruction, TypeDescription loadedType, Assigner assigner, boolean considerRuntimeType) { super(assigner, considerRuntimeType); this.valueLoadInstruction = valueLoadInstruction; this.loadedType = loadedType; } @Override public Instrumentation withAssigner(Assigner assigner, boolean considerRuntimeType) { return new ForPoolValue(valueLoadInstruction, loadedType, nonNull(assigner), considerRuntimeType); } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } @Override public ByteCodeAppender appender(Target instrumentationTarget) { return this; } @Override public boolean appendsCode() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Context instrumentationContext, MethodDescription instrumentedMethod) { return apply(methodVisitor, instrumentationContext, instrumentedMethod, loadedType, valueLoadInstruction); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && super.equals(other) && loadedType.equals(((ForPoolValue) other).loadedType) && valueLoadInstruction.equals(((ForPoolValue) other).valueLoadInstruction); } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + valueLoadInstruction.hashCode(); result = 31 * result + loadedType.hashCode(); return result; } @Override public String toString() { return "FixedValue.ForPoolValue{" + "valueLoadInstruction=" + valueLoadInstruction + ", loadedType=" + loadedType + ", assigner=" + assigner + ", considerRuntimeType=" + considerRuntimeType + '}'; } } /** * A fixed value instrumentation that represents its fixed value as a static field of the instrumented class. */ protected static class ForStaticField extends FixedValue implements AssignerConfigurable { /** * The prefix of the static field that is created for storing the fixed value. */ private static final String PREFIX = "fixedValue"; /** * The name of the field in which the fixed value is stored. */ private final String fieldName; /** * The value that is to be stored in the static field. */ private final Object fixedValue; /** * The type if the field for storing the fixed value. */ private final TypeDescription fieldType; /** * Creates a new static field fixed value instrumentation with a random name for the field containing the fixed * value. * * @param fixedValue The fixed value to be returned. * @param assigner The assigner to use for assigning the fixed value to the return type of the * instrumented value. * @param considerRuntimeType If {@code true}, the runtime type of the given value will be considered for * assigning the return type. */ protected ForStaticField(Object fixedValue, Assigner assigner, boolean considerRuntimeType) { this(String.format("%s$%d", PREFIX, Math.abs(fixedValue.hashCode())), fixedValue, assigner, considerRuntimeType); } /** * Creates a new static field fixed value instrumentation. * * @param fieldName The name of the field for storing the fixed value. * @param fixedValue The fixed value to be returned. * @param assigner The assigner to use for assigning the fixed value to the return type of the * instrumented value. * @param considerRuntimeType If {@code true}, the runtime type of the given value will be considered for * assigning the return type. */ protected ForStaticField(String fieldName, Object fixedValue, Assigner assigner, boolean considerRuntimeType) { super(assigner, considerRuntimeType); this.fieldName = fieldName; this.fixedValue = fixedValue; fieldType = new TypeDescription.ForLoadedType(fixedValue.getClass()); } @Override public Instrumentation withAssigner(Assigner assigner, boolean considerRuntimeType) { return new ForStaticField(fieldName, fixedValue, nonNull(assigner), considerRuntimeType); } @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType .withField(fieldName, fieldType, Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC) .withInitializer(LoadedTypeInitializer.ForStaticField.nonAccessible(fieldName, fixedValue)); } @Override public ByteCodeAppender appender(Target instrumentationTarget) { return new StaticFieldByteCodeAppender(instrumentationTarget.getTypeDescription()); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && fieldName.equals(((ForStaticField) other).fieldName) && fixedValue.equals(((ForStaticField) other).fixedValue) && super.equals(other); } @Override public int hashCode() { return 31 * 31 * super.hashCode() + 31 * fieldName.hashCode() + fixedValue.hashCode(); } @Override public String toString() { return "FixedValue.ForStaticField{" + "fieldName='" + fieldName + '\'' + ", fieldType=" + fieldType + ", fixedValue=" + fixedValue + ", assigner=" + assigner + ", considerRuntimeType=" + considerRuntimeType + '}'; } /** * A byte code appender for returning the fixed value that was stored in a static field. */ private class StaticFieldByteCodeAppender implements ByteCodeAppender { /** * The stack manipulation that loads the fixed value onto the operand stack. */ private final StackManipulation fieldGetAccess; /** * Creates a new byte code appender for returning a value of a static field from an instrumented method. * * @param instrumentedType The instrumented type that is subject of the instrumentation. */ private StaticFieldByteCodeAppender(TypeDescription instrumentedType) { fieldGetAccess = FieldAccess.forField(instrumentedType.getDeclaredFields() .filter((named(fieldName))).getOnly()).getter(); } @Override public boolean appendsCode() { return true; } @Override public Size apply(MethodVisitor methodVisitor, Context instrumentationContext, MethodDescription instrumentedMethod) { return ForStaticField.this.apply(methodVisitor, instrumentationContext, instrumentedMethod, fieldType, fieldGetAccess); } @Override public boolean equals(Object other) { return this == other || !(other == null || getClass() != other.getClass()) && fieldGetAccess.equals(((StaticFieldByteCodeAppender) other).fieldGetAccess); } @Override public int hashCode() { return fieldGetAccess.hashCode(); } @Override public String toString() { return "StaticFieldByteCodeAppender{fieldGetAccess=" + fieldGetAccess + '}'; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy