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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 + '}';
}
}
Loading

0 comments on commit 22be227

Please sign in to comment.