Skip to content

Commit

Permalink
Improved support for pre-Java 5 byte code.
Browse files Browse the repository at this point in the history
  • Loading branch information
raphw committed Apr 21, 2016
1 parent 79142bc commit 22be227
Show file tree
Hide file tree
Showing 24 changed files with 400 additions and 522 deletions.
7 changes: 7 additions & 0 deletions byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java
Expand Up @@ -76,6 +76,13 @@
* older class file format version.
* </p>
* <p>
* <b>Note</b>: For the purpose of inlining, Java 5 and Java 6 byte code can be seen as the best candidate for advice methods. These versions do
* no longer allow subroutines, neither do they already allow invokedynamic instructions or method handles. This way, Java 5 and Java 6 byte
* code is compatible to both older and newer versions. One exception for backwards-incompatible byte code is the possibility to load type references
* from the constant pool onto the operand stack. These instructions can however easily be transformerd for classes compiled to Java 4 and older
* by registering a {@link TypeConstantAdjustment} <b>before</b> the advice visitor.
* </p>
* <p>
* <b>Note</b>: It is not possible to trigger break points in inlined advice methods as the debugging information of the inlined advice is not
* preserved. It is not possible in Java to reference more than one source file per class what makes translating such debugging information
* impossible. It is however possible to set break points in advice methods when invoking the original advice target. This allows debugging
Expand Down
Expand Up @@ -3014,6 +3014,7 @@ public void visit(int classFileVersionNumber,
TypeDescription.OBJECT :
instrumentedType.getSuperClass().asErasure()).getInternalName(),
instrumentedType.getInterfaces().asErasures().toInternalNames());
implementationContext.setClassFileVersion(ClassFileVersion.ofMinorMajor(classFileVersionNumber));
typeAttributeAppender.apply(cv, instrumentedType, annotationValueFilterFactory.on(instrumentedType));
if (!ClassFileVersion.ofMinorMajor(classFileVersionNumber).isAtLeast(ClassFileVersion.JAVA_V8) && instrumentedType.isInterface()) {
implementationContext.prohibitTypeInitializer();
Expand Down Expand Up @@ -3496,6 +3497,7 @@ public byte[] create(Implementation.Context.ExtractableView implementationContex
? TypeDescription.OBJECT
: instrumentedType.getSuperClass().asErasure()).getInternalName(),
instrumentedType.getInterfaces().asErasures().toInternalNames());
implementationContext.setClassFileVersion(classFileVersion);
typeAttributeAppender.apply(classVisitor, instrumentedType, annotationValueFilterFactory.on(instrumentedType));
for (FieldDescription fieldDescription : instrumentedType.getDeclaredFields()) {
fieldPool.target(fieldDescription).apply(classVisitor, annotationValueFilterFactory);
Expand Down
Expand Up @@ -57,6 +57,10 @@ protected FixedValue(Assigner assigner, Assigner.Typing typing) {
* When a value is stored in the class's constant pool, its identity is lost. If an object's identity is important, the
* {@link FixedValue#reference(Object)} method should be used instead.
* </p>
* <p>
* <b>Important</b>: When supplying a method handle or a method type, all types that are implied must be visible to the instrumented
* type or an {@link IllegalAccessException} will be thrown at runtime.
* </p>
*
* @param fixedValue The fixed value to return from the method.
* @return An implementation for the given {@code fixedValue}.
Expand Down Expand Up @@ -114,12 +118,12 @@ public static AssignerConfigurable value(Object fixedValue) {
Assigner.DEFAULT,
Assigner.Typing.STATIC);
} else if (JavaType.METHOD_HANDLE.getTypeStub().isAssignableFrom(type)) {
return new ForPoolValue(MethodHandleConstant.of(JavaInstance.MethodHandle.ofLoaded(fixedValue)),
return new ForPoolValue(new JavaInstanceConstant(JavaInstance.MethodHandle.ofLoaded(fixedValue)),
new TypeDescription.ForLoadedType(type),
Assigner.DEFAULT,
Assigner.Typing.STATIC);
} else if (JavaType.METHOD_TYPE.getTypeStub().represents(type)) {
return new ForPoolValue(MethodTypeConstant.of(JavaInstance.MethodType.ofLoaded(fixedValue)),
return new ForPoolValue(new JavaInstanceConstant(JavaInstance.MethodType.ofLoaded(fixedValue)),
new TypeDescription.ForLoadedType(type),
Assigner.DEFAULT,
Assigner.Typing.STATIC);
Expand Down
Expand Up @@ -406,12 +406,34 @@ interface Context {
*/
FieldDescription.InDefinedShape cache(StackManipulation fieldValue, TypeDescription fieldType);

/**
* Returns the instrumented type of the current implementation. The instrumented type is exposed with the intend of allowing optimal
* byte code generation and not for implementing checks or changing the behavior of a {@link StackManipulation}.
*
* @return The instrumented type of the current implementation.
*/
TypeDescription getInstrumentedType();

/**
* Returns the class file version of the current implementation.
*
* @return The class file version of the current implementation.
*/
ClassFileVersion getClassFileVersion();

/**
* Represents an extractable view of an {@link Implementation.Context} which
* allows the retrieval of any registered auxiliary type.
*/
interface ExtractableView extends Context {

/**
* Sets the class file version this implementation context should represent.
*
* @param classFileVersion The class file version to represent.
*/
void setClassFileVersion(ClassFileVersion classFileVersion);

/**
* Determines if this implementation context allows for the retention of a static type initializer.
*
Expand Down Expand Up @@ -496,6 +518,49 @@ public String toString() {
}
}
}

/**
* An abstract base implementation of an extractable view of an implementation context.
*/
abstract class AbstractBase implements ExtractableView {

/**
* The instrumented type.
*/
protected final TypeDescription instrumentedType;

/**
* The class file version of the instrumented type.
*/
private ClassFileVersion classFileVersion;

/**
* Create a new extractable view.
*
* @param instrumentedType The instrumented type.
*/
protected AbstractBase(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
}

@Override
public void setClassFileVersion(ClassFileVersion classFileVersion) {
this.classFileVersion = classFileVersion;
}

@Override
public TypeDescription getInstrumentedType() {
return instrumentedType;
}

@Override
public ClassFileVersion getClassFileVersion() {
if (classFileVersion == null) {
throw new IllegalStateException("Cannot read class file version before it was set");
}
return classFileVersion;
}
}
}

/**
Expand Down Expand Up @@ -523,20 +588,15 @@ ExtractableView make(TypeDescription instrumentedType,
* redefining a class when it is not allowed to add methods to a class what is an implicit requirement when copying the static
* initializer block into another method.
*/
class Disabled implements ExtractableView {

/**
* The instrumented type.
*/
private final TypeDescription instrumentedType;
class Disabled extends ExtractableView.AbstractBase {

/**
* Creates a new disabled implementation context.
*
* @param instrumentedType The instrumented type.
*/
protected Disabled(TypeDescription instrumentedType) {
this.instrumentedType = instrumentedType;
super(instrumentedType);
}

@Override
Expand Down Expand Up @@ -624,7 +684,7 @@ public String toString() {
* A default implementation of an {@link Implementation.Context.ExtractableView}
* which serves as its own {@link net.bytebuddy.implementation.auxiliary.AuxiliaryType.MethodAccessorFactory}.
*/
class Default implements ExtractableView, AuxiliaryType.MethodAccessorFactory {
class Default extends ExtractableView.AbstractBase implements AuxiliaryType.MethodAccessorFactory {

/**
* The name suffix to be appended to an accessor method.
Expand All @@ -636,11 +696,6 @@ class Default implements ExtractableView, AuxiliaryType.MethodAccessorFactory {
*/
public static final String FIELD_CACHE_PREFIX = "cachedValue";

/**
* The instrumented type that this instance represents.
*/
private final TypeDescription instrumentedType;

/**
* The type initializer of the created instrumented type.
*/
Expand Down Expand Up @@ -714,7 +769,7 @@ protected Default(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
TypeInitializer typeInitializer,
ClassFileVersion classFileVersion) {
this.instrumentedType = instrumentedType;
super(instrumentedType);
this.auxiliaryTypeNamingStrategy = auxiliaryTypeNamingStrategy;
this.typeInitializer = typeInitializer;
this.classFileVersion = classFileVersion;
Expand Down
Expand Up @@ -8,13 +8,15 @@
import net.bytebuddy.implementation.bind.MethodDelegationBinder;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.constant.*;
import net.bytebuddy.utility.JavaInstance;
import net.bytebuddy.utility.JavaType;

import java.lang.annotation.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
* <p>
* The origin annotation provides some meta information about the source method that is bound to this method where
* the binding is dependant of the parameter's type:
* <ol>
Expand All @@ -36,6 +38,11 @@
* is injected. Method type descriptions are only supported for byte code versions starting from Java 7.</li>
* </ol>
* Any other parameter type will cause an {@link java.lang.IllegalStateException}.
* </p>
* <p>
* <b>Important:</b> A method handle or method type reference can only be used if the referenced method's types are all visible
* to the instrumented type or an {@link IllegalAccessError} will be thrown at runtime.
* </p>
*
* @see net.bytebuddy.implementation.MethodDelegation
* @see net.bytebuddy.implementation.bind.annotation.TargetMethodAnnotationDrivenBinder
Expand Down Expand Up @@ -104,9 +111,9 @@ public MethodDelegationBinder.ParameterBinding<?> bind(AnnotationDescription.Loa
} else if (parameterType.represents(int.class)) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(IntegerConstant.forValue(source.getModifiers()));
} else if (parameterType.equals(JavaType.METHOD_HANDLE.getTypeStub())) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(MethodHandleConstant.of(source.asDefined()));
return new MethodDelegationBinder.ParameterBinding.Anonymous(JavaInstance.MethodHandle.of(source.asDefined()).asStackManipulation());
} else if (parameterType.equals(JavaType.METHOD_TYPE.getTypeStub())) {
return new MethodDelegationBinder.ParameterBinding.Anonymous(MethodTypeConstant.of(source.asDefined()));
return new MethodDelegationBinder.ParameterBinding.Anonymous(JavaInstance.MethodType.of(source.asDefined()).asStackManipulation());
} else {
throw new IllegalStateException("The " + target + " method's " + target.getIndex() +
" parameter is annotated with a Origin annotation with an argument not representing a Class," +
Expand Down
Expand Up @@ -197,6 +197,10 @@ ParameterBinding<?> bind(AnnotationDescription.Loadable<T> annotation,
* instances or method handles and method types for classes of a version at least of Java 7. The latter instances can also be
* expressed as unloaded {@link JavaInstance} representations.
* </p>
* <p>
* <b>Important</b>: When supplying a method handle or a method type, all types that are implied must be visible to the instrumented
* type or an {@link IllegalAccessException} will be thrown at runtime.
* </p>
*
* @param <S> The bound annotation's type.
*/
Expand Down Expand Up @@ -248,16 +252,16 @@ public ParameterBinding<?> bind(AnnotationDescription.Loadable<S> annotation,
stackManipulation = ClassConstant.of((TypeDescription) value);
suppliedType = TypeDescription.CLASS;
} else if (JavaType.METHOD_HANDLE.getTypeStub().isInstance(value)) {
stackManipulation = MethodHandleConstant.of(JavaInstance.MethodHandle.ofLoaded(value));
stackManipulation = JavaInstance.MethodHandle.ofLoaded(value).asStackManipulation();
suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
} else if (value instanceof JavaInstance.MethodHandle) {
stackManipulation = MethodHandleConstant.of((JavaInstance.MethodHandle) value);
stackManipulation = new JavaInstanceConstant((JavaInstance.MethodHandle) value);
suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
} else if (JavaType.METHOD_TYPE.getTypeStub().isInstance(value)) {
stackManipulation = MethodTypeConstant.of(JavaInstance.MethodType.ofLoaded(value));
stackManipulation = new JavaInstanceConstant(JavaInstance.MethodType.ofLoaded(value));
suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
} else if (value instanceof JavaInstance.MethodType) {
stackManipulation = MethodTypeConstant.of((JavaInstance.MethodType) value);
stackManipulation = new JavaInstanceConstant((JavaInstance.MethodType) value);
suppliedType = JavaType.METHOD_HANDLE.getTypeStub();
} else {
throw new IllegalStateException("Not able to save in class's constant pool: " + value);
Expand Down
@@ -1,5 +1,6 @@
package net.bytebuddy.implementation.bytecode.constant;

import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
Expand Down Expand Up @@ -160,7 +161,12 @@ public boolean isValid() {

@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
methodVisitor.visitLdcInsn(Type.getType(typeDescription.getDescriptor()));
if (implementationContext.getClassFileVersion().isAtLeast(ClassFileVersion.JAVA_V5) && typeDescription.isVisibleTo(implementationContext.getInstrumentedType())) {
methodVisitor.visitLdcInsn(Type.getType(typeDescription.getDescriptor()));
} else {
methodVisitor.visitLdcInsn(typeDescription.getName());
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false);
}
return SIZE;
}

Expand Down
@@ -0,0 +1,54 @@
package net.bytebuddy.implementation.bytecode.constant;

import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.StackSize;
import net.bytebuddy.utility.JavaInstance;
import org.objectweb.asm.MethodVisitor;

/**
* A constant representing a {@link JavaInstance}.
*/
public class JavaInstanceConstant implements StackManipulation {

/**
* The instance to load onto the operand stack.
*/
private final JavaInstance javaInstance;

/**
* Creates a constant pool value representing a {@link JavaInstance}.
*
* @param javaInstance The instance to load onto the operand stack.
*/
public JavaInstanceConstant(JavaInstance javaInstance) {
this.javaInstance = javaInstance;
}

@Override
public boolean isValid() {
return true;
}

@Override
public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext) {
methodVisitor.visitLdcInsn(javaInstance.asConstantPoolValue());
return StackSize.SINGLE.toIncreasingSize();
}

@Override
public boolean equals(Object other) {
return this == other || !(other == null || getClass() != other.getClass())
&& javaInstance.equals(((JavaInstanceConstant) other).javaInstance);
}

@Override
public int hashCode() {
return javaInstance.hashCode();
}

@Override
public String toString() {
return "JavaInstanceConstant{javaInstance=" + javaInstance + '}';
}
}

0 comments on commit 22be227

Please sign in to comment.