
org.opalj.br.instructions.ClassFileFactory.scala Maven / Gradle / Ivy
The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br
package instructions
import scala.annotation.switch
import org.opalj.log.OPALLogger
import org.opalj.log.GlobalLogContext
import org.opalj.bi.ACC_BRIDGE
import org.opalj.bi.ACC_PUBLIC
import org.opalj.bi.ACC_SYNTHETIC
import org.opalj.br.MethodDescriptor.DefaultConstructorDescriptor
import scala.collection.immutable.ArraySeq
/**
* Provides helper methods to facilitate the generation of classes.
* In particular, functionality to create transparent proxy classes is provided.
*
* @author Arne Lottmann
*/
object ClassFileFactory {
/**
* Name used to store the final receiver object in generated proxy classes.
*/
final val ReceiverFieldName = "$receiver"
/**
* This is the default name for the factory method of a proxy class that is created by
* the [[Proxy]] method. If the name of the method to be proxified is equal to this
* name, [[AlternativeFactoryMethodName]] is used instead.
*/
final val DefaultFactoryMethodName = "$newInstance"
/**
* Alternative name for the factory method of proxy classes created by the [[Proxy]]
* method. This name is only used if the proxified method's name is equal to
* [[DefaultFactoryMethodName]].
*/
final val AlternativeFactoryMethodName = "$createInstance"
/**
* Creates a class that acts as a proxy for the specified class.
* The proxy implements a single method – e.g., as defined by a so-called
* "Functional Interface" - that calls the specified method;
* creating a proxy for `java.lang.Object`'s methods is not supported. Additionally,
* further marker interfaces (e.g., `java.io.Serializable`) may be implemented.
*
* The generated class uses the following template:
* {{{
* class
* extends
* implements {
*
* private final receiver;
*
* // possible additional fields for static parameters
*
* public ""( receiver) { // the constructor
* this.receiver = receiver;
* }
*
* public {
* return/*<= if the return type is not void*/ this.receiver.()
* }
*
* // possibly multiple bridge methods (multiple only in case of altLambdaMetaFactory usages)
* }
* }}}
*
* The class, the constructor and the method are public. The field which holds
* the receiver object is private and final unless the receiver method is static.
* In this case no receiver field is generated and the constructor
* does not take an argument of the receiver's type.
*
* In addition to the receiver field, additional fields holding '''static parameters'''
* are created if all parameters found in `methodDescriptor` are present, in the same
* order, at the end of `receiverMethodDescriptor`'s parameters, but
* `receiverMethodDescriptor` has more parameters that precede the parameters found in
* `methodDescriptor`.
*
* E.g., given the following two descriptors:
* {{{
* val methodDescriptor =
* MethodDescriptor(IntegerType, IntegerType)
* val receiverMethodDescriptor =
* MethodDescriptor(IndexedSeq(DoubleType, IntegerType), IntegerType)
* }}}
* one additional field and constructor parameter of type `double` will be created.
* This case occurs for example with Java 8 lambda expressions that capture local
* variables, which are prepended to the regular parameter list.
*
* If any of the parameters or the return type of `methodDescriptor` are
* generic types, the generated proxy will need to create a bridge method to be valid.
* Therefore, in these cases, `bridgeMethodDescriptor` must be specified.
* It must be identical to `methodDescriptor` except for all occurrences of generic
* types, which must be replaced with `ObjectType.Object`.
* For example, consider the Java interface `java.util.Comparator` that defines the
* generic type `T` and uses it in its `int compare(T, T)` method. This would require
* a bridge method `int compare(Object, Object)`. The appropriate method descriptors
* for, for example, `Comparator` would be:
* {{{
* // Uses "String"
* methodDescriptor =
* MethodDescriptor(IndexedSeq(ObjectType.String, ObjectType.String), IntegerType)
* // Uses "Object"
* bridgeMethodDescriptor =
* MethodDescriptor(IndexedSeq(ObjectType.Object, ObjectType.Object), IntegerType)
* }}}
*
* The created class will always have its synthetic access flag set, as well as the
* [[VirtualTypeFlag]] attribute.
*
* @note The used class file version is 52. (StackMapTables are, however, still not required
* because the code contains no relevant control-flow.)
*
* @note It is expected that `methodDescriptor` and `receiverMethodDescriptor` are
* "compatible", i.e., it would be possible to have the method described by
* `methodDescriptor` forward to `receiverMethodDescriptor`.
*
* This requires that for their return types, one of the following statements holds true:
*
* - `methodDescriptor`'s return type is [[VoidType]] (so no returning is necessary)
* - `receiverMethodDescriptor`'s return type is assignable to `methodDescriptor`'s
* (e.g., a "smaller" numerical type, (un)boxable, a subtype, etc)
* - `receiverMethodDescriptor` returns `Object`: in this case, we assume that `Object`
* stands for "generic return type" and expect the receiver method to return an
* object of a type compatible to the forwarder method's return type
*
* Additionally, the parameter lists must satisfy one of these conditions:
*
* - they are identical
* - the descriptors have the same numbers of parameters and `methodDescriptor`'s
* parameter types can be widened/boxed/unboxed to match `receiverMethodDescriptor`'s
* parameter types
* - `methodDescriptor`'s first parameter is of the same type as `receiverType`,
* and the remaining parameters are compatible to `receiverMethodDescriptor`'s
* entire parameter list (this is, effectively, an explicit `this` and occurs for
* example with references to instance methods: e.g., `String::isEmpty`, a zero
* argument method, could be turned into the Predicate method `test(String)`)
* - the last `n` parameters of `receiverMethodDescriptor` are identical to the
* parameters of `methodDescriptor`, where `n = methodDescriptor.parametersCount`
* (this is the case if a lambda expression captures local variables)
* - `receiverMethodDescriptor`'s single parameter is of type `Object[]` (in this case,
* `methodDescriptor`'s arguments will be collected into an `Object[]` prior to
* forwarding)
*
* Examples of compatible method descriptors are:
* {{{
* // ------------- First Example
* methodDescriptor =
* MethodDescriptor(IntegerType, VoidType)
* receiverMethodDescriptor =
* MethodDescriptor(ObjectType.Integer, VoidType)
* // or MethodDescriptor(ObjectType.Object, ByteType)
*
* // ------------- Second Example
* methodDescriptor =
* MethodDescriptor(ObjectType.String, BooleanType)
* receiverMethodDescriptor =
* MethodDescriptor.JustReturnsBoolean // IF receiverType == ObjectType.String
*
* // ------------- Third Example
* methodDescriptor =
* MethodDescriptor(IndexedSeq(ByteType, ByteType, ObjectType.Integer), IntegerType)
* receiverMethodDescriptor =
* MethodDescriptor(ArrayType.ArrayOfObject, ObjectType.Object) // generic method
*
* // ------------- Fourth Example
* methodDescriptor =
* MethodDescriptor(IntegerType, LongType)
* receiverMethodDescriptor =
* MethodDescriptor(IndexedSeq(ByteType, ByteType, IntegerType), IntegerType)
* }}}
*
* @param definingType The defining type; if the type is `Serializable`, the interface '''has
* to be a direct super interface'''.
*
* @param invocationInstruction the opcode of the invocation instruction
* (`INVOKESPECIAL.opcode`,`INVOKEVIRTUAL.opcode`,
* `INVOKESTATIC.opcode`,`INVOKEINTERFACE.opcode`)
* used to call call the method on the receiver.
*/
def Proxy(
caller: ObjectType,
callerIsInterface: Boolean,
definingType: TypeDeclaration,
methodName: String,
methodDescriptor: MethodDescriptor,
receiverType: ObjectType,
receiverIsInterface: Boolean,
implMethod: MethodCallMethodHandle,
invocationInstruction: Opcode,
samMethodType: MethodDescriptor,
bridgeMethodDescriptors: MethodDescriptors
): ClassFile = {
val interfaceMethodParametersCount = methodDescriptor.parametersCount
val implMethodParameters = implMethod.methodDescriptor.parameterTypes
val receiverField =
if (invocationInstruction == INVOKESTATIC.opcode ||
isNewInvokeSpecial(invocationInstruction, implMethod.name) ||
isVirtualMethodReference(
invocationInstruction,
receiverType,
implMethod.methodDescriptor,
methodDescriptor
)) {
ArraySeq.empty
} else {
ArraySeq(createField(fieldType = receiverType, name = ReceiverFieldName))
}
val additionalFieldsForStaticParameters =
implMethodParameters.dropRight(interfaceMethodParametersCount).zipWithIndex.map[FieldTemplate] { p =>
val (fieldType, index) = p
createField(fieldType = fieldType, name = s"staticParameter$index")
}
val fields: ArraySeq[FieldTemplate] =
receiverField ++ additionalFieldsForStaticParameters
val constructor: MethodTemplate = createConstructor(definingType, fields)
val factoryMethodName: String =
if (methodName == DefaultFactoryMethodName) {
AlternativeFactoryMethodName
} else {
DefaultFactoryMethodName
}
/* We don't need to analyze the type hierarchy because the "Serializable" interface is
* directly implemented by the defining type.
* From "java...LambdaMetaFactory":
* When FLAG_SERIALIZABLE is set in flags, the function objects will implement
* Serializable, and will have a writeReplace method that returns an appropriate
* SerializedLambda. The caller class must have an appropriate $deserializeLambda$
* method, as described in SerializedLambda.
*/
val isSerializable = definingType.theSuperinterfaceTypes.contains(ObjectType.Serializable)
val methods = new Array[AnyRef](
3 /* proxy method, constructor, factory */ +
bridgeMethodDescriptors.length + // bridge methods
(if (isSerializable) 2 else 0) // writeReplace and $deserializeLambda$ if Serializable
)
methods(0) = proxyMethod(
definingType.objectType,
methodName,
methodDescriptor,
additionalFieldsForStaticParameters,
receiverType,
receiverIsInterface,
implMethod,
invocationInstruction
)
methods(1) = constructor
methods(2) = createFactoryMethod(
definingType.objectType,
fields.map[FieldType](_.fieldType),
factoryMethodName
)
bridgeMethodDescriptors.iterator.zipWithIndex.foreach {
case (bridgeMethodDescriptor, i) =>
methods(3 + i) = createBridgeMethod(
methodName,
bridgeMethodDescriptor,
methodDescriptor,
definingType.objectType
)
}
// Add a writeReplace and $deserializeLambda$ method if the class isSerializable
if (isSerializable) {
methods(3 + bridgeMethodDescriptors.length) = createWriteReplaceMethod(
definingType,
methodName,
samMethodType,
implMethod,
methodDescriptor,
additionalFieldsForStaticParameters
)
methods(4 + bridgeMethodDescriptors.length) = createDeserializeLambdaProxy(
caller,
callerIsInterface
)
}
// We need a version 52 classfile, because prior version don't support an INVOKESTATIC
// instruction on a static interface method.
// Given that none of the generated methods contains any control-flow instructions
// (gotos, ifs, switches, ...) we don't have to create a StackmapTableAttribute.
ClassFile(
0, 52,
bi.ACC_SYNTHETIC.mask | bi.ACC_PUBLIC.mask | bi.ACC_SUPER.mask,
definingType.objectType,
definingType.theSuperclassType,
definingType.theSuperinterfaceTypes.toArraySeq,
fields,
ArraySeq.unsafeWrapArray(methods).asInstanceOf[MethodTemplates],
ArraySeq(VirtualTypeFlag)
)
}
def DeserializeLambdaProxy(
definingType: TypeDeclaration,
bootstrapArguments: BootstrapArguments,
staticMethodName: String
): ClassFile = {
/*
Instructions of LambdaDeserialize::bootstrap. This method will be reimplemented in the
constructor of the new LambdaDeserializeProxy class.
PC Line Instruction
0 36 invokestatic java.lang.invoke.MethodHandles { java.lang.invoke.MethodHandles$Lookup lookup () }
3 | astore_2
4 38 ldc java.lang.Object.class
6 39 ldc java.lang.invoke.SerializedLambda.class
8 | invokestatic java.lang.invoke.MethodType { java.lang.invoke.MethodType methodType (java.lang.Class, java.lang.Class) }
11 | astore_3
12 42 aload_2
13 43 aload_0
14 | invokevirtual java.lang.invoke.SerializedLambda { java.lang.String getImplMethodName () }
17 44 aload_3
18 | iconst_1
19 | anewarray java.lang.invoke.MethodHandle
22 | dup
23 | iconst_0
24 45 aconst_null
25 | aastore
26 | invokestatic scala.runtime.LambdaDeserialize { java.lang.invoke.CallSite bootstrap (java.lang.invoke.MethodHandles$Lookup, java.lang.String, java.lang.invoke.MethodType, java.lang.invoke.MethodHandle[]) }
29 | astore 4
31 59 aload 4
33 | invokevirtual java.lang.invoke.CallSite { java.lang.invoke.MethodHandle getTarget () }
36 | aload_0
37 | invokevirtual java.lang.invoke.MethodHandle { java.lang.Object invoke (java.lang.invoke.SerializedLambda) }
40 | areturn
*/
// val a = new ArrayBuffer[Object](100)
var buildMethodType: Array[Instruction] = Array() // IMPROVE [L10] Use ArrayBuffer
bootstrapArguments.iterator.zipWithIndex.foreach { ia =>
val (arg, idx) = ia
val staticHandle = arg.asInstanceOf[InvokeStaticMethodHandle]
// lookup.findStatic parameters
def getParameterTypeInstruction(t: Type): Array[Instruction] =
if (t.isReferenceType) {
Array(LDC(ConstantClass(t.asReferenceType)), null)
} else if (t.isBaseType) {
// Primitive type handling
Array(
GETSTATIC(t.asBaseType.WrapperType, "TYPE", ObjectType.Class),
null, null
)
} else {
// Handling for void type
Array(
GETSTATIC(
VoidType.WrapperType,
"TYPE",
ObjectType.Class
), null, null
)
}
buildMethodType ++= Array(DUP)
buildMethodType ++= getParameterTypeInstruction(staticHandle.methodDescriptor.returnType) // rtype
if (staticHandle.methodDescriptor.parametersCount == 0) {
// We have ZERO parameters, call MethodType.methodType with return parameter only
// IMPROVE: check if LDC method type can be used
buildMethodType ++= Array(
INVOKESTATIC(
ObjectType.MethodType,
false,
"methodType",
MethodDescriptor(
ArraySeq(ObjectType.Class),
ObjectType.MethodType
)
), null, null
)
} else if (staticHandle.methodDescriptor.parametersCount == 1) {
// We have ONE parameters, call MethodType.methodType with return and one parameter only
buildMethodType ++= getParameterTypeInstruction(staticHandle.methodDescriptor.parameterType(0))
buildMethodType ++= Array(
INVOKESTATIC(
ObjectType.MethodType,
false,
"methodType",
MethodDescriptor(
ArraySeq(ObjectType.Class, ObjectType.Class),
ObjectType.MethodType
)
), null, null
)
} else {
// We have MULTIPLE parameters
buildMethodType ++= getParameterTypeInstruction(staticHandle.methodDescriptor.parameterType(0))
buildMethodType ++= Array(
// The first parameter has its own parameter field
ICONST_4,
ANEWARRAY(ObjectType.Class), null, null
)
// The following parameters are put into an array of Class
staticHandle.methodDescriptor.parameterTypes.tail.zipWithIndex.foreach { pt =>
val (param, i) = pt
buildMethodType ++= Array(
DUP,
BIPUSH(i), null // Use BIPUSH instead of ICONST_, it is equivalent, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.iconst_i
) ++ getParameterTypeInstruction(param) ++ Array(
AASTORE
)
}
buildMethodType ++= Array(
INVOKESTATIC(
ObjectType.MethodType,
false,
"methodType",
MethodDescriptor(
ArraySeq(
ObjectType.Class,
ObjectType.Class,
ArrayType(ObjectType.Class)
),
ObjectType.MethodType
)
), null, null
)
}
buildMethodType ++= Array(
LDC(ConstantString(staticHandle.name)), null, // method name
LDC(ConstantClass(staticHandle.receiverType)), null // reference class
)
// Now we have to call lookup.getStatic
buildMethodType ++= Array(
ALOAD_2, // Load the lookup
INVOKEVIRTUAL(
ObjectType.MethodHandles$Lookup,
"getStatic",
MethodDescriptor(
ArraySeq(
ObjectType.Class,
ObjectType.String,
ObjectType.MethodType
),
ObjectType.MethodHandle
)
), null, null,
LoadInt(idx), null,
AASTORE
)
}
val instructions: Array[Instruction] =
Array(
INVOKESTATIC(
ObjectType.MethodHandles,
false,
"lookup",
MethodDescriptor.withNoArgs(ObjectType.MethodHandles$Lookup)
), null, null,
ASTORE_2,
LoadClass(ObjectType.Object), null,
LoadClass(ObjectType.SerializedLambda), null,
INVOKESTATIC(
ObjectType.MethodType,
false,
"methodType",
MethodDescriptor(
ArraySeq(ObjectType.Class, ObjectType.Class), ObjectType.MethodType
)
), null, null,
ASTORE_3,
ALOAD_2,
ALOAD_0,
INVOKEVIRTUAL(
ObjectType.SerializedLambda,
"getImplMethodName",
MethodDescriptor.JustReturnsString
), null, null,
ALOAD_3,
LoadInt(bootstrapArguments.length), null,
ANEWARRAY(ObjectType.MethodHandle), null, null
) ++
// *** START Add lookup for each argument ***
buildMethodType ++
// *** END Add lookup for each argument ***
Array(
INVOKESTATIC(
ObjectType.ScalaLambdaDeserialize,
false,
"bootstrap",
MethodDescriptor(
ArraySeq(
ObjectType.MethodHandles$Lookup,
ObjectType.String,
ObjectType.MethodType,
ArrayType(ObjectType.MethodHandle)
),
ObjectType.CallSite
)
), null, null,
ASTORE(4), null,
ALOAD(4), null,
INVOKEVIRTUAL(
ObjectType.CallSite,
"getTarget",
MethodDescriptor.withNoArgs(ObjectType.MethodHandle)
), null, null,
ALOAD_0,
INVOKEVIRTUAL(
ObjectType.MethodHandle,
"invoke",
MethodDescriptor(
ObjectType.SerializedLambda, // Parameter
ObjectType.Object // Return
)
), null, null,
ARETURN
)
val maxStack = Code.computeMaxStack(instructions)
val methods = ArraySeq(
Method(
bi.ACC_PUBLIC.mask | bi.ACC_STATIC.mask,
staticMethodName,
MethodDescriptor(
ArraySeq(ObjectType.SerializedLambda),
ObjectType.Object
),
ArraySeq(
Code(maxStack, maxLocals = 5, instructions, NoExceptionHandlers, NoAttributes)
)
)
)
// We need a version 52 classfile, because prior version don't support an INVOKESTATIC
// instruction on a static interface method.
// Given that none of the generated methods contains any control-flow instructions
// (gotos, ifs, switches, ...) we don't have to create a StackmapTableAttribute.
ClassFile(
0, 52,
bi.ACC_SYNTHETIC.mask | bi.ACC_PUBLIC.mask | bi.ACC_SUPER.mask,
definingType.objectType,
definingType.theSuperclassType,
definingType.theSuperinterfaceTypes.toArraySeq,
NoFieldTemplates, // Class fields
methods,
ArraySeq(VirtualTypeFlag)
)
}
/**
* Returns true if the method invocation described by the given Opcode and method name
* is a "NewInvokeSpecial" invocation (i.e., a reference to a constructor, like so:
* `Object::new`).
*/
def isNewInvokeSpecial(opcode: Opcode, methodName: String): Boolean = {
opcode == INVOKESPECIAL.opcode && methodName == ""
}
/**
* Returns true if the given parameters identify a Java 8 method reference to an
* instance or interface method (i.e., a reference to a virtual method, like so:
* `ArrayList::size` or `List::size`). In this case, the resulting functional interface's method
* has one parameter more than the referenced method because the referenced method's
* implicit `this` parameter becomes explicit.
*/
def isVirtualMethodReference(
opcode: Opcode,
targetMethodDeclaringType: ObjectType,
targetMethodDescriptor: MethodDescriptor,
proxyInterfaceMethodDescriptor: MethodDescriptor
): Boolean = {
(opcode == INVOKEVIRTUAL.opcode || opcode == INVOKEINTERFACE.opcode) &&
targetMethodDescriptor.parametersCount + 1 ==
proxyInterfaceMethodDescriptor.parametersCount &&
(proxyInterfaceMethodDescriptor.parameterType(0) eq targetMethodDeclaringType)
}
/**
* Creates a field of the specified type with the given name.
*/
def createField(
accessFlags: Int = bi.ACC_PRIVATE.mask | bi.ACC_FINAL.mask,
name: String,
fieldType: FieldType,
attributes: Attributes = NoAttributes
): FieldTemplate = {
Field(accessFlags, name, fieldType, attributes)
}
/**
* Creates a public constructor that initializes the given fields.
*
* For every `Field` in `fields` the constructor will have one parameter of the same
* type. The parameter list will have the same order as `fields`.
* The generated constructor will call the superclass' default constructor; i.e.,
* the type `definingType.theSuperclassType` has to have a default constructor.
* Additionally, bytecode is generated to populate the `fields` from the constructor
* arguments.
*
* @see [[callSuperDefaultConstructor]]
* @see [[copyParametersToInstanceFields]]
*/
def createConstructor(
definingType: TypeDeclaration,
fields: FieldTemplates
): MethodTemplate = {
// it doesn't make sense that the superClassType is not defined
val theSuperclassType = definingType.theSuperclassType.get
val theType = definingType.objectType
val instructions =
callSuperDefaultConstructor(theSuperclassType) ++
copyParametersToInstanceFields(theType, fields) :+
RETURN
val maxStack =
1 /* for `this` when invoking the super constructor*/ + (
if (fields.isEmpty)
0 // nothing extra needed if no fields are being set
/*
* For below: we're only setting one field at a time, so we'll only need
* two additional stack slots if there's at least one field to be set that
* needs two spaces. Otherwise, we just need one more space.
*/
else if (fields.exists(_.fieldType.computationalType.operandSize == 2))
2
else
1
)
val maxLocals = 1 + fields.iterator.map(_.fieldType.computationalType.operandSize).sum
Method(
bi.ACC_PUBLIC.mask,
"",
MethodDescriptor(fields.map[FieldType](_.fieldType), VoidType),
ArraySeq(Code(maxStack, maxLocals, instructions, NoExceptionHandlers, NoAttributes))
)
}
/**
* Returns the instructions necessary to perform a call to the constructor of the
* given superclass.
*/
def callSuperDefaultConstructor(theSuperclassType: ObjectType): Array[Instruction] = {
Array(
ALOAD_0,
INVOKESPECIAL(theSuperclassType, false, "", DefaultConstructorDescriptor),
null, null
)
}
/**
* Creates an array of instructions that populates the given `fields` in `declaringType`
* from local variables (constructor parameters).
*
* This method assumes that it creates instructions for a constructor whose parameter
* list matches the given `fields` in terms of order and field types.
*
* It further assumes that none of the `fields` provided as arguments are
* static fields, as it would make little sense to initialize static fields through
* the constructor.
*/
def copyParametersToInstanceFields(
declaringType: ObjectType,
fields: FieldTemplates
): Array[Instruction] = {
val requiredInstructions =
computeNumberOfInstructionsForParameterLoading(fields.map[FieldType](_.fieldType), 1) +
fields.size + // ALOAD_0 for each field
(3 * fields.size) // PUTFIELD for each field
val instructions = new Array[Instruction](requiredInstructions)
var currentInstructionPC = 0
var nextLocalVariableIndex = 1
fields foreach { f =>
instructions(currentInstructionPC) = ALOAD_0
currentInstructionPC = ALOAD_0.indexOfNextInstruction(currentInstructionPC, false)
val llvi = LoadLocalVariableInstruction(f.fieldType, nextLocalVariableIndex)
nextLocalVariableIndex += f.fieldType.computationalType.operandSize
instructions(currentInstructionPC) = llvi
currentInstructionPC = llvi.indexOfNextInstruction(currentInstructionPC, false)
val putField = PUTFIELD(declaringType, f.name, f.fieldType)
instructions(currentInstructionPC) = putField
currentInstructionPC = putField.indexOfNextInstruction(currentInstructionPC, false)
}
instructions
}
/**
* Computes the number of instructions required to put the list of parameters given
* as `fieldTypes` onto the stack.
*/
private def computeNumberOfInstructionsForParameterLoading(
fieldTypes: FieldTypes,
localVariableOffset: Int
): Int = {
var numberOfInstructions = 0
var localVariableIndex = localVariableOffset
fieldTypes foreach { ft =>
numberOfInstructions += (if (localVariableIndex <= 3) 1 else 2)
localVariableIndex += ft.computationalType.operandSize
}
numberOfInstructions
}
/**
* Creates a factory method with the appropriate instructions to create and
* return an instance of `typeToCreate`.
*
* `typeToCreate` must have a constructor with a parameter list that exactly matches
* `fieldTypes`. It also must not define a method named `factoryMethodName` with a
* parameter list matching `fieldTypes`.
*
* @see [[createConstructor]]
*/
def createFactoryMethod(
typeToCreate: ObjectType,
fieldTypes: FieldTypes,
factoryMethodName: String
): MethodTemplate = {
val numberOfInstructionsForParameterLoading: Int =
computeNumberOfInstructionsForParameterLoading(fieldTypes, 0)
val numberOfInstructions =
3 + // NEW
1 + // DUP
numberOfInstructionsForParameterLoading +
3 + // INVOKESPECIAL
1 // ARETURN
val maxLocals = fieldTypes.iterator.map(_.computationalType.operandSize.toInt).sum
val maxStack = maxLocals + 2 // new + dup makes two extra on the stack
val instructions = new Array[Instruction](numberOfInstructions)
var currentPC: Int = 0
instructions(currentPC) = NEW(typeToCreate)
currentPC = instructions(currentPC).indexOfNextInstruction(currentPC, false)
instructions(currentPC) = DUP
currentPC = instructions(currentPC).indexOfNextInstruction(currentPC, false)
var currentVariableIndex = 0
fieldTypes.foreach { t =>
val instruction = LoadLocalVariableInstruction(t, currentVariableIndex)
currentVariableIndex += t.computationalType.operandSize
instructions(currentPC) = instruction
currentPC = instruction.indexOfNextInstruction(currentPC, false)
}
instructions(currentPC) =
INVOKESPECIAL(typeToCreate, false, "", MethodDescriptor(fieldTypes, VoidType))
currentPC = instructions(currentPC).indexOfNextInstruction(currentPC, false)
instructions(currentPC) = ARETURN
val body = Code(maxStack, maxLocals, instructions)
Method(
bi.ACC_PUBLIC.mask | bi.ACC_STATIC.mask,
factoryMethodName,
MethodDescriptor(fieldTypes, typeToCreate),
ArraySeq(body)
)
}
/**
* Creates the `writeReplace` method for a lambda proxy class. It is used
* to create a `SerializedLambda` object which holds the serialized lambda.
*
* The parameters of the SerializedLambda class map are as following:
* capturingClass = definingType
* functionalInterfaceClass = definingType.theSuperinterfaceTypes.head (This is the functional interface)
* functionalInterfaceMethodName = functionalInterfaceMethodName
* functionalInterfaceMethodSignature = samMethodType.parameterType
* implMethodKind = implMethod match to REF_
* implClass = implMethod.receiverType
* implMethodName = implMethod.name
* implMethodSignature = implMethod.methodDescriptor
* instantiatedMethodType = instantiatedMethodType
* capturedArgs = methodDescriptor.parameterTypes (factoryParameters, fields in Proxy)
*
* @see https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/SerializedLambda.html
* and
* https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html#altMetafactory-java.lang.invoke.MethodHandles.Lookup-java.lang.String-java.lang.invoke.MethodType-java.lang.Object...-
*
* @param definingType The lambda proxyclass type.
* @param methodName The name of the functional interface method.
* @param samMethodType Includes the method signature for the functional interface method.
* @param implMethod The lambda method inside the class defining the lambda.
* @param additionalFieldsForStaticParameters The static fields that are put into the capturedArgs.
* @return A SerializedLambda object containing all information to be able to serialize this lambda.
*/
def createWriteReplaceMethod(
definingType: TypeDeclaration,
methodName: String,
samMethodType: MethodDescriptor,
implMethod: MethodCallMethodHandle,
instantiatedMethodType: MethodDescriptor,
additionalFieldsForStaticParameters: FieldTemplates
): MethodTemplate = {
var instructions: Array[Instruction] = Array(
NEW(ObjectType.SerializedLambda), null, null,
DUP,
LoadClass(definingType.objectType), null,
LDC(ConstantString(definingType.theSuperinterfaceTypes.head.fqn)), null,
LDC(ConstantString(methodName)), null,
LDC(ConstantString(samMethodType.toJVMDescriptor)), null,
LDC(ConstantInteger(implMethod.referenceKind.referenceKind)), null,
LDC(ConstantString(implMethod.receiverType.asObjectType.fqn)), null,
LDC(ConstantString(implMethod.name)), null,
LDC(ConstantString(implMethod.methodDescriptor.toJVMDescriptor)), null,
LDC(ConstantString(instantiatedMethodType.toJVMDescriptor)), null,
// Add the capturedArgs
BIPUSH(additionalFieldsForStaticParameters.length), null,
ANEWARRAY(ObjectType.Object), null, null
)
additionalFieldsForStaticParameters.zipWithIndex.foreach {
case (x, i) =>
instructions ++= Array(
DUP,
BIPUSH(i), null,
ALOAD_0,
GETFIELD(definingType.objectType, x.name, x.fieldType), null, null
)
if (x.fieldType.isBaseType) {
instructions ++= x.fieldType.asBaseType.boxValue
}
instructions :+= AASTORE
}
instructions ++= Array(
INVOKESPECIAL(
ObjectType.SerializedLambda,
isInterface = false,
"",
MethodDescriptor(
ArraySeq(
ObjectType.Class,
ObjectType.String,
ObjectType.String,
ObjectType.String,
IntegerType,
ObjectType.String,
ObjectType.String,
ObjectType.String,
ObjectType.String,
ArrayType(ObjectType.Object)
),
VoidType
)
), null, null,
ARETURN
)
val maxStack = Code.computeMaxStack(instructions)
val body = Code(maxStack, 1, instructions)
Method(
bi.ACC_PUBLIC.mask | bi.ACC_SYNTHETIC.mask,
"writeReplace",
MethodDescriptor.JustReturnsObject,
ArraySeq(body)
)
}
/**
* Creates a static proxy method used by the `\$deserializeLambda$` method.
*
* @param caller The class where the lambda is implemented.
* @param callerIsInterface `true `if the class is an interface, false if not.
* @return The static proxy method relaying the `\$deserializedLambda$` invocation to the actual
* class that implements the lambda.
*/
def createDeserializeLambdaProxy(
caller: ObjectType,
callerIsInterface: Boolean
): MethodTemplate = {
val deserializedLambdaMethodDescriptor = MethodDescriptor(
ObjectType.SerializedLambda,
ObjectType.Object
)
val instructions: Array[Instruction] = Array(
ALOAD_0,
INVOKESTATIC(
caller,
callerIsInterface,
"$deserializeLambda$",
deserializedLambdaMethodDescriptor
), null, null,
ARETURN
)
val maxStack = Code.computeMaxStack(instructions)
val body = Code(maxStack, 1, instructions)
Method(
bi.ACC_PUBLIC.mask | bi.ACC_STATIC.mask | bi.ACC_SYNTHETIC.mask,
"$deserializeLambda$",
deserializedLambdaMethodDescriptor,
ArraySeq(body)
)
}
/**
* Creates a proxy method with name `methodName` and descriptor `methodDescriptor` and
* the bytecode instructions to execute the method `receiverMethod` in `receiverType`.
*
* The `methodDescriptor`s have to be identical in terms of parameter types and have to
* be compatible w.r.t. the return type.
*/
def proxyMethod(
definingType: ObjectType,
methodName: String,
methodDescriptor: MethodDescriptor,
staticParameters: Seq[FieldTemplate],
receiverType: ObjectType,
receiverIsInterface: Boolean,
implMethod: MethodCallMethodHandle,
invocationInstruction: Opcode
): MethodTemplate = {
val code =
createProxyMethodBytecode(
definingType, methodDescriptor, staticParameters,
receiverType, receiverIsInterface, implMethod,
invocationInstruction
)
Method(bi.ACC_PUBLIC.mask, methodName, methodDescriptor, ArraySeq(code))
}
/**
* Creates the bytecode instructions for the proxy method.
*
* These instructions will setup the stack with the variables required to call the
* `receiverMethod`, perform the appropriate invocation instruction (one of
* INVOKESTATIC, INVOKEVIRTUAL, or INVOKESPECIAL), and return from the proxy method.
*
* @see [[parameterForwardingInstructions]]
*/
private def createProxyMethodBytecode(
definingType: ObjectType, // type of "this"
methodDescriptor: MethodDescriptor, // the parameters of the current method
staticParameters: Seq[FieldTemplate],
receiverType: ObjectType,
receiverIsInterface: Boolean,
implMethod: MethodCallMethodHandle,
invocationInstruction: Opcode
): Code = {
assert(!receiverIsInterface || invocationInstruction != INVOKEVIRTUAL.opcode)
// If we have a constructor, we have to fix the method descriptor. Usually, the instruction
// doesn't have a return type. For the proxy method, it is important to return the instance
// so we have to patch the correct return type into the method descriptor.
val fixedImplDescriptor: MethodDescriptor =
if (implMethod.name == "" && invocationInstruction == INVOKESPECIAL.opcode) {
MethodDescriptor(implMethod.methodDescriptor.parameterTypes, receiverType)
} else {
implMethod.methodDescriptor
}
val isVirtualMethodReference = this.isVirtualMethodReference(
invocationInstruction,
receiverType,
fixedImplDescriptor,
methodDescriptor
)
// if the receiver method is not static, we need to push the receiver object
// onto the stack, which we can retrieve from the receiver field on `this`
// unless we have a method reference where the receiver will be explicitly
// provided
val loadReceiverObject: Array[Instruction] =
if (invocationInstruction == INVOKESTATIC.opcode || isVirtualMethodReference) {
Array()
} else if (implMethod.name == "") {
Array(
NEW(receiverType), null, null,
DUP
)
} else {
Array(
ALOAD_0,
GETFIELD(definingType, ReceiverFieldName, receiverType), null, null
)
}
// `this` occupies variable 0, since the proxy method is never static
val variableOffset = 1
val forwardParametersInstructions =
parameterForwardingInstructions(
methodDescriptor, fixedImplDescriptor, variableOffset,
staticParameters, definingType
)
val forwardingCallInstruction: Array[Instruction] = (invocationInstruction: @switch) match {
case INVOKESTATIC.opcode =>
Array(
INVOKESTATIC(
implMethod.receiverType.asObjectType, receiverIsInterface,
implMethod.name, fixedImplDescriptor
), null, null
)
case INVOKESPECIAL.opcode =>
val methodDescriptor =
if (implMethod.name == "") {
MethodDescriptor(fixedImplDescriptor.parameterTypes, VoidType)
} else {
fixedImplDescriptor
}
val invoke = INVOKESPECIAL(
implMethod.receiverType.asObjectType, receiverIsInterface,
implMethod.name, methodDescriptor
)
Array(invoke, null, null)
case INVOKEINTERFACE.opcode =>
val invoke = INVOKEINTERFACE(implMethod.receiverType.asObjectType, implMethod.name, fixedImplDescriptor)
Array(invoke, null, null, null, null)
case INVOKEVIRTUAL.opcode =>
val invoke = INVOKEVIRTUAL(implMethod.receiverType, implMethod.name, fixedImplDescriptor)
Array(invoke, null, null)
}
val forwardingInstructions: Array[Instruction] =
forwardParametersInstructions ++ forwardingCallInstruction
val returnAndConvertInstructionsArray: Array[Instruction] =
if (methodDescriptor.returnType.isVoidType) {
if (fixedImplDescriptor.returnType.isVoidType)
Array(RETURN)
else
Array(
if (fixedImplDescriptor.returnType.operandSize == 1) POP else POP2,
RETURN
)
} else {
returnAndConvertInstructions(
methodDescriptor.returnType.asFieldType,
fixedImplDescriptor.returnType.asFieldType
)
}
val bytecodeInstructions: Array[Instruction] =
loadReceiverObject ++ forwardingInstructions ++ returnAndConvertInstructionsArray
val receiverObjectStackSize =
if (invocationInstruction == INVOKESTATIC.opcode) 0 else 1
val parametersStackSize = implMethod.methodDescriptor.requiredRegisters
val returnValueStackSize = methodDescriptor.returnType.operandSize
val maxStack =
1 + // Required if, e.g., we first have to create and initialize an object;
// which is done by "dup"licating the new created, but not yet initialized
// object reference on the stack.
math.max(receiverObjectStackSize + parametersStackSize, returnValueStackSize)
val maxLocals =
1 +
receiverObjectStackSize + parametersStackSize + returnValueStackSize
Code(maxStack, maxLocals, bytecodeInstructions, NoExceptionHandlers, NoAttributes)
}
/**
* Generates an array of instructions that fill the operand stack with all parameters
* required by `receiverMethodDescriptor` from the parameters of
* `calledMethodDescriptor`. For that reason, it is expected that both method
* descriptors have compatible parameter and return types: i.e., that
* `forwarderMethodDescriptor`'s parameters can be widened or (un)boxed to fit into
* `receiverMethodDescriptor`'s parameters, and that `receiverMethodDescriptor`'s return
* type can be widened or (un)boxed to fit into `forwarderMethodDescriptor`'s return type.
*
* If `receiverMethodDescriptor` has more parameters than `forwarderMethodDescriptor`,
* the missing parameters must be provided in `staticParameters`.
*
* @see [[org.opalj.br.instructions.LoadLocalVariableInstruction]]
*/
def parameterForwardingInstructions(
forwarderMethodDescriptor: MethodDescriptor,
receiverMethodDescriptor: MethodDescriptor,
variableOffset: Int,
staticParameters: Seq[FieldTemplate],
definingType: ObjectType
): Array[Instruction] = try {
val receiverParameters = receiverMethodDescriptor.parameterTypes
val forwarderParameters = forwarderMethodDescriptor.parameterTypes
var lvIndex = variableOffset
val receiverTakesObjectArray =
receiverParameters.size == 1 && receiverParameters(0) == ArrayType(ObjectType.Object)
val callerDoesNotTakeObjectArray =
forwarderParameters.isEmpty || forwarderParameters(0) != ArrayType(ObjectType.Object)
if (receiverTakesObjectArray && callerDoesNotTakeObjectArray) {
// now we need to construct a new object[] array on the stack
// and shove everything (boxed, if necessary) in there
val arraySize = forwarderParameters.size
var numberOfInstructions = 3 + // need to create a new object array
LoadConstantInstruction(arraySize).length
forwarderParameters.zipWithIndex foreach { p =>
val (t, i) = p
val loadInstructions = if (lvIndex > 3) 2 else 1
// primitive types need 3 for a boxing (invokestatic)
val conversionInstructions = if (t.isBaseType) 3 else 0
val storeInstructions =
1 + // dup
LoadConstantInstruction(i).length +
1 // aastore
numberOfInstructions += loadInstructions + conversionInstructions +
storeInstructions
lvIndex += t.computationalType.operandSize
}
val instructions = new Array[Instruction](numberOfInstructions)
var nextIndex = 0
lvIndex = variableOffset
instructions(nextIndex) = LoadConstantInstruction(arraySize)
nextIndex = instructions(nextIndex).indexOfNextInstruction(nextIndex, false)
instructions(nextIndex) = ANEWARRAY(ObjectType.Object)
nextIndex = instructions(nextIndex).indexOfNextInstruction(nextIndex, false)
forwarderParameters.zipWithIndex foreach { p =>
val (t, i) = p
instructions(nextIndex) = DUP
nextIndex = instructions(nextIndex).indexOfNextInstruction(nextIndex, false)
instructions(nextIndex) = LoadConstantInstruction(i)
nextIndex = instructions(nextIndex).indexOfNextInstruction(nextIndex, false)
val llv = LoadLocalVariableInstruction(t, lvIndex)
lvIndex += t.computationalType.operandSize
instructions(nextIndex) = llv
nextIndex = instructions(nextIndex).indexOfNextInstruction(nextIndex, false)
if (t.isBaseType) {
// boxing always needs one instruction
val boxInstructions = t.asBaseType.boxValue
val boxInstruction = boxInstructions(0)
instructions(nextIndex) = boxInstruction
nextIndex = instructions(nextIndex).indexOfNextInstruction(nextIndex, false)
}
instructions(nextIndex) = AASTORE
nextIndex = instructions(nextIndex).indexOfNextInstruction(nextIndex, false)
}
instructions
} else {
val staticParametersCount = staticParameters.size
val forwarderParametersCount = forwarderParameters.size
val receiverParametersCount = receiverParameters.size
var forwarderParameterIndex = 0
var numberOfInstructions = 4 * staticParametersCount
var receiverParametersOffset = staticParametersCount
if (forwarderParametersCount > receiverParametersCount) {
/* This is the case of an implicit receiver becoming explicit in the
* forwarder, hence we get one instruction for loading the first
* parameter. Subsequently we need to ignore that parameter, though.
*/
numberOfInstructions += 1
lvIndex += forwarderParameters(0).computationalType.operandSize
forwarderParameterIndex = 1
receiverParametersOffset = -1
}
while (forwarderParameterIndex < forwarderParametersCount) {
val ft = forwarderParameters(forwarderParameterIndex)
val rt = receiverParameters(forwarderParameterIndex + receiverParametersOffset)
val loadInstructions = if (lvIndex > 3) 2 else 1
lvIndex += ft.computationalType.operandSize
val conversionInstructions =
if (rt != ft) {
if (rt.isBaseType && ft.isBaseType) {
if (rt.isIntLikeType && ft.isIntLikeType &&
rt.asIntLikeType.isWiderThan(ft.asIntLikeType)) {
0 // smaller int values can directly be stored in "wider" types
} else {
1 // we only do safe conversions => 1 instruction
}
} else if ((rt.isBaseType && ft.isObjectType) || (ft.isBaseType && rt.isObjectType)) {
// can (un)box to fit => invokestatic/invokevirtual
3
} else if (rt.isReferenceType && ft.isReferenceType && (rt ne ObjectType.Object)) {
3 // checkcast
} else 0
} else 0
numberOfInstructions += loadInstructions + conversionInstructions
forwarderParameterIndex += 1
}
val instructions = new Array[Instruction](numberOfInstructions)
lvIndex = variableOffset
var currentIndex = 0
var receiverParameterIndex = 0
if (receiverParametersOffset == -1) {
// we have an explicit receiver in the forwarder parameters
val llv = LoadLocalVariableInstruction(forwarderParameters(0), 1)
instructions(currentIndex) = llv
currentIndex = llv.indexOfNextInstruction(currentIndex, false)
lvIndex += 1
}
while (receiverParameterIndex < receiverParametersCount) {
val rt = receiverParameters(receiverParameterIndex)
if (receiverParameterIndex < staticParametersCount) {
val sp = staticParameters(receiverParameterIndex)
instructions(currentIndex) = ALOAD_0
currentIndex = ALOAD_0.indexOfNextInstruction(currentIndex, false)
val gf = GETFIELD(definingType, sp.name, sp.fieldType)
instructions(currentIndex) = gf
currentIndex = gf.indexOfNextInstruction(currentIndex, false)
} else {
val ft = forwarderParameters(receiverParameterIndex - receiverParametersOffset)
val llv = LoadLocalVariableInstruction(ft, lvIndex)
instructions(currentIndex) = llv
lvIndex += ft.computationalType.operandSize
currentIndex = llv.indexOfNextInstruction(currentIndex, false)
if (rt != ft) {
if (ft.isNumericType && rt.isNumericType) {
val conversion = ft.asNumericType.convertTo(rt.asNumericType)
conversion foreach { instr =>
instructions(currentIndex) = instr
currentIndex = instr.indexOfNextInstruction(currentIndex, false)
}
} else if (rt.isReferenceType && ft.isReferenceType) {
if (rt ne ObjectType.Object) {
val conversion = CHECKCAST(rt.asReferenceType)
instructions(currentIndex) = conversion
currentIndex = conversion.indexOfNextInstruction(currentIndex, false)
}
} else if (rt.isBaseType && ft.isObjectType) {
val unboxInstructions = ft.asObjectType.unboxValue
val unboxInstruction = unboxInstructions(0)
instructions(currentIndex) = unboxInstruction
currentIndex = unboxInstruction.indexOfNextInstruction(currentIndex, false)
} else if (ft.isBaseType && rt.isObjectType) {
val boxInstructions = ft.asBaseType.boxValue
val boxInstruction = boxInstructions(0)
instructions(currentIndex) = boxInstruction
currentIndex = boxInstruction.indexOfNextInstruction(currentIndex, false)
} else {
throw new UnknownError("Should not occur: "+ft+" -> "+rt)
}
}
}
receiverParameterIndex += 1
}
instructions
}
} catch {
case t: Throwable =>
OPALLogger.error(
"internal error",
s"${definingType.toJava}: failed to create parameter forwarding instructions for:\n\t"+
s"forwarder descriptor = ${forwarderMethodDescriptor.toJava} =>\n\t"+
s"receiver descriptor = $receiverMethodDescriptor +\n\t "+
s"static parameters = $staticParameters (variableOffset=$variableOffset)",
t
)(GlobalLogContext)
throw t;
}
/**
* Returns the instructions that return a value of type `typeToBeReturned`, converting
* `typeOnStack` to it first if necessary.
* If `typeOnStack` is `Object`, it will be treated as a generic return type and
* converted to the required type.
*/
@throws[IllegalArgumentException]("if `typeOnStack` is not compatible with `toBeReturnedType` and `typeOnStack` is not `Object`")
def returnAndConvertInstructions(
toBeReturnedType: FieldType,
typeOnStack: FieldType
): Array[Instruction] = {
if (toBeReturnedType eq typeOnStack)
return Array(ReturnInstruction(toBeReturnedType))
val conversionInstructions: Array[Instruction] =
if (typeOnStack eq ObjectType.Object) {
if (toBeReturnedType.isBaseType) {
val baseType = toBeReturnedType.asBaseType
val wrapper = baseType.WrapperType
Array(CHECKCAST(wrapper), null, null) ++ wrapper.unboxValue
} else {
Array(CHECKCAST(toBeReturnedType.asReferenceType), null, null)
}
} else if (typeOnStack.isObjectType && toBeReturnedType.isObjectType) {
Array(CHECKCAST(toBeReturnedType.asObjectType), null, null)
} else if (typeOnStack.isNumericType && toBeReturnedType.isNumericType) {
typeOnStack.asNumericType.convertTo(toBeReturnedType.asNumericType)
} else if (typeOnStack.isNumericType && toBeReturnedType.isObjectType) {
typeOnStack.asNumericType.boxValue
} else if (typeOnStack.isObjectType && toBeReturnedType.isNumericType) {
typeOnStack.asObjectType.unboxValue
} else if (typeOnStack.isBooleanType && toBeReturnedType.isObjectType) {
typeOnStack.asBooleanType.boxValue
} else if (typeOnStack.isObjectType && toBeReturnedType.isBooleanType) {
typeOnStack.asObjectType.unboxValue
} else if (typeOnStack.isArrayType && (typeOnStack.asArrayType.elementType eq ObjectType.Object)
&& toBeReturnedType.isArrayType &&
typeOnStack.asArrayType.dimensions <= toBeReturnedType.asArrayType.dimensions) {
Array(CHECKCAST(toBeReturnedType.asReferenceType), null, null)
} else {
throw new IllegalArgumentException(
s"incompatible types: ${toBeReturnedType.toJava} and ${typeOnStack.toJava}"
)
}
conversionInstructions :+ ReturnInstruction(toBeReturnedType)
}
/**
* Creates a bridge method using the given method descriptors, name, and type.
*
* The bridge method's parameter list and return type are dictated by
* `bridgeMethodDescriptor`.
* This method generates bytecode that invokes the method described by `methodName`
* and `targetMethodDescriptor` on `declaringType`. If parameters need to be cast
* before invocation, the appropriate bytecode will be generated as well.
*/
def createBridgeMethod(
methodName: String,
bridgeMethodDescriptor: MethodDescriptor,
targetMethodDescriptor: MethodDescriptor,
targetMethodDeclaringType: ObjectType
): MethodTemplate = {
val bridgeMethodParameters = bridgeMethodDescriptor.parameterTypes
val targetMethodParameters = targetMethodDescriptor.parameterTypes
val bridgeMethodParametersCount = bridgeMethodParameters.size
var numberOfInstructions = 1 // for ALOAD_0
var parameterIndex = 0
var lvIndex = 1
while (parameterIndex < bridgeMethodParametersCount) {
val parameter = bridgeMethodParameters(parameterIndex)
val target = targetMethodParameters(parameterIndex)
val loadInstructions = if (lvIndex > 3) 2 else 1
val conversionInstructions = if (parameter != target) 3 else 0
numberOfInstructions += loadInstructions + conversionInstructions
parameterIndex += 1
lvIndex += parameter.computationalType.operandSize.toInt
}
numberOfInstructions += 3 // invoke target method
numberOfInstructions += 1 // return
val instructions = new Array[Instruction](numberOfInstructions)
var currentPC = 0
instructions(currentPC) = ALOAD_0
currentPC = ALOAD_0.indexOfNextInstruction(currentPC, false)
parameterIndex = 0
lvIndex = 1
while (parameterIndex < bridgeMethodParametersCount) {
val parameter = bridgeMethodParameters(parameterIndex)
val target = targetMethodParameters(parameterIndex)
val llv = LoadLocalVariableInstruction(parameter, lvIndex)
instructions(currentPC) = llv
currentPC = llv.indexOfNextInstruction(currentPC, false)
if (parameter != target) {
val cast = CHECKCAST(target.asReferenceType)
instructions(currentPC) = cast
currentPC = cast.indexOfNextInstruction(currentPC, false)
}
parameterIndex += 1
lvIndex += parameter.computationalType.operandSize
}
val invokeTarget = INVOKEVIRTUAL(targetMethodDeclaringType, methodName,
targetMethodDescriptor)
instructions(currentPC) = invokeTarget
currentPC = invokeTarget.indexOfNextInstruction(currentPC, false)
val returnInstruction = ReturnInstruction(bridgeMethodDescriptor.returnType)
instructions(currentPC) = returnInstruction
val maxStack = targetMethodDescriptor.requiredRegisters + 1 //<= the receiver
val maxLocals = maxStack + targetMethodDescriptor.returnType.operandSize
Method(
ACC_PUBLIC.mask | ACC_BRIDGE.mask | ACC_SYNTHETIC.mask,
methodName,
bridgeMethodDescriptor.parameterTypes,
bridgeMethodDescriptor.returnType,
ArraySeq(Code(maxStack, maxLocals, instructions, NoExceptionHandlers, NoAttributes))
)
}
def cloneMethodSignature: MethodSignature = {
MethodSignature("clone", MethodDescriptor.withNoArgs(ObjectType.Object))
}
def equalsMethodSignature: MethodSignature = {
MethodSignature("equals", MethodDescriptor(ObjectType.Object, BooleanType))
}
def finalizeMethodSignature: MethodSignature = {
MethodSignature("equals", MethodDescriptor.NoArgsAndReturnVoid)
}
def hashCodeMethodSignature: MethodSignature = {
MethodSignature("hashCode", MethodDescriptor.withNoArgs(IntegerType))
}
def toStringSignature: MethodSignature = {
MethodSignature("toString", MethodDescriptor.withNoArgs(ObjectType.String))
}
def nonFinalInterfaceOfObject(): Array[MethodSignature] = {
Array(
cloneMethodSignature,
equalsMethodSignature,
finalizeMethodSignature,
hashCodeMethodSignature,
toStringSignature
)
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy