
org.opalj.br.reader.BootstrapArgumentLoading.scala Maven / Gradle / Ivy
The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br
package reader
import org.opalj.bi.ACC_PRIVATE
import org.opalj.bi.ACC_STATIC
import org.opalj.bi.ACC_SYNTHETIC
import org.opalj.br.collection.mutable.InstructionsBuilder
import org.opalj.br.instructions.AASTORE
import org.opalj.br.instructions.ACONST_NULL
import org.opalj.br.instructions.ALOAD_1
import org.opalj.br.instructions.ANEWARRAY
import org.opalj.br.instructions.CHECKCAST
import org.opalj.br.instructions.DUP
import org.opalj.br.instructions.GETSTATIC
import org.opalj.br.instructions.IAND
import org.opalj.br.instructions.ICONST_0
import org.opalj.br.instructions.ICONST_1
import org.opalj.br.instructions.IFNE
import org.opalj.br.instructions.INVOKESTATIC
import org.opalj.br.instructions.INVOKEVIRTUAL
import org.opalj.br.instructions.LoadClass_W
import org.opalj.br.instructions.LoadConstantInstruction
import org.opalj.br.instructions.LoadDynamic2_W
import org.opalj.br.instructions.LoadDynamic_W
import org.opalj.br.instructions.LoadMethodHandle_W
import org.opalj.br.instructions.LoadString_W
import org.opalj.br.instructions.TypeConversionInstructions
import org.opalj.br.MethodDescriptor.JustReturnsString
import org.opalj.br.cp.Constant_Pool
import org.opalj.br.instructions.ALOAD_0
import org.opalj.br.instructions.ALOAD_2
import org.opalj.br.instructions.ARETURN
import org.opalj.br.instructions.ASTORE_1
import org.opalj.br.instructions.ASTORE_2
import org.opalj.br.instructions.BIPUSH
import org.opalj.br.instructions.IADD
import org.opalj.br.instructions.IFEQ
import org.opalj.br.instructions.IMUL
import org.opalj.br.instructions.INSTANCEOF
import org.opalj.br.instructions.INVOKESPECIAL
import org.opalj.br.instructions.IRETURN
import org.opalj.br.instructions.LoadInt_W
import org.opalj.br.instructions.NEW
import scala.collection.immutable.ArraySeq
/**
* Provides functionality to produce bytecode that loads a bootstrap argument. Loading of dynamic
* constants is rewritten to not use ldc/ldc_w/ldc2_w instructions where possible.
*
* @author Dominik Helm
*/
trait BootstrapArgumentLoading {
/**
* Generate instructions to load a constant argument for a bootstrap method.
*
* @param argument The argument to be loaded
* @param instructions The instruction builder the instructions are appended to
* @param classFile The class file the constant is in
* @param boxed If true, ensures the argument is of a reference type, boxing it if necessary
*
* @return The maximum stack height the generated instructions require and the (potentially
* new!) class file
*/
def loadBootstrapArgument(
argument: ConstantValue[_],
instructions: InstructionsBuilder,
classFile: ClassFile,
boxed: Boolean = false
): (Int, ClassFile) = {
argument match {
case DynamicConstant(bootstrapMethod, name, descriptor) =>
loadDynamicConstant(
bootstrapMethod, name, descriptor, instructions, classFile, boxed
)
case _ =>
instructions ++= LoadConstantInstruction(argument, wide = true)
if (boxed && argument.runtimeValueType.isBaseType)
instructions ++= argument.runtimeValueType.asBaseType.boxValue
(argument.runtimeValueType.computationalType.operandSize, classFile)
}
}
/**
* Generate instructions to load a dynamic constant.
*
* @param bootstrapMethod The constant's bootstrap method
* @param name The constant's name information, passed as the bootstrap method's 2. argument
* @param descriptor The constant's type, passed as the bootstrap method's 3. argument
* @param instructions The instruction builder the instructions are appended to
* @param classFile The class file the constant is in
* @param boxed If true, ensures the constant is of a reference type, boxing it if necessary
*
* @return The maximum stack height the generated instructions require and the (potentially
* new!) class file
*/
def loadDynamicConstant(
bootstrapMethod: BootstrapMethod,
name: String,
descriptor: FieldType,
instructions: InstructionsBuilder,
classFile: ClassFile,
boxed: Boolean = false
): (Int, ClassFile) = {
def dynamicLoad(): LoadConstantInstruction[_] = {
if (descriptor.computationalType.isCategory2) LoadDynamic2_W(bootstrapMethod, name, descriptor)
else LoadDynamic_W(bootstrapMethod, name, descriptor)
}
bootstrapMethod match {
case BootstrapMethod(InvokeStaticMethodHandle(ObjectType.ConstantBootstraps, false, methodName, _), args: ArraySeq[ConstantValue[_]] @unchecked) =>
methodName match {
case "arrayVarHandle" =>
val maxStackAndClassFile = loadClassType(args.head, instructions, classFile)
instructions ++= INVOKESTATIC(
ObjectType.MethodHandles,
isInterface = false,
"arrayElementVarHandle",
MethodDescriptor(ObjectType.Class, ObjectType.VarHandle)
)
maxStackAndClassFile
case "enumConstant" =>
instructions ++= GETSTATIC(descriptor.asObjectType, name, descriptor)
(1, classFile)
case "explicitCast" =>
val argument = args.head
val argType = argument.runtimeValueType
val (bsaStack, updatedClassFile) =
loadBootstrapArgument(argument, instructions, classFile)
val maxStack = descriptor match {
case ObjectType.Object => // Nothing to do here, but box if required
if (boxed && argType.isBaseType)
instructions ++= argType.asBaseType.boxValue
bsaStack
case rt: ReferenceType =>
instructions ++= CHECKCAST(rt)
bsaStack
case bt: BaseType =>
val isBool = bt.isBooleanType
val targetType = if (isBool) IntegerType else bt
val baseType = if (argType.isReferenceType) {
instructions ++= ObjectType.unboxValue(argType)
ObjectType.primitiveType(argType.asObjectType).get.asNumericType
} else argType.asNumericType
if (baseType != targetType) {
instructions ++= baseType.convertTo(targetType.asNumericType)
}
// explicitCast requires booleans to be converted from ints based on
// the least significant bit
if (isBool) {
instructions ++= ICONST_1
instructions ++= IAND
}
if (boxed)
instructions ++= bt.boxValue
bsaStack + (if (isBool) 1 else 0)
}
(maxStack, updatedClassFile)
case "fieldVarHandle" =>
fieldVarHandle(
name,
args.head,
cf => loadClassType(args(1), instructions, cf),
isStaticField = false,
instructions,
classFile
)
case "getStaticFinal" =>
if (args.isEmpty) { // Simple version loads field from class of field type
val declClass = if (descriptor.isObjectType) descriptor.asObjectType
else descriptor.asBaseType.WrapperType
instructions ++= GETSTATIC(declClass, name, descriptor)
if (boxed && descriptor.isBaseType)
instructions ++= descriptor.asBaseType.boxValue
(descriptor.computationalType.operandSize, classFile)
} else args.head match {
case ConstantClass(ct) =>
instructions ++= GETSTATIC(ct.asObjectType, name, descriptor)
if (boxed && descriptor.isBaseType)
instructions ++= descriptor.asBaseType.boxValue
(descriptor.computationalType.operandSize, classFile)
case dc: DynamicConstant =>
doGetStatic(dc, name, descriptor, instructions, classFile, boxed)
}
case "invoke" =>
val methodHandle = args.head
val arguments = args.tail
invokeMethodHandle(
methodHandle, arguments, descriptor, instructions, classFile, boxed
)
case "nullConstant" =>
instructions ++= ACONST_NULL
(1, classFile)
case "primitiveClass" =>
val classType = FieldType(name).asBaseType.WrapperType
instructions ++= GETSTATIC(classType, "TYPE", ObjectType.Class)
(1, classFile)
case "staticFieldVarHandle" =>
fieldVarHandle(
name,
args.head,
cf => loadClassType(args(1), instructions, cf),
isStaticField = true,
instructions,
classFile
)
case _ =>
instructions ++= dynamicLoad()
(descriptor.computationalType.operandSize, classFile)
}
case BootstrapMethod(InvokeStaticMethodHandle(ObjectType.ObjectMethods, false, _, _), _) =>
val newMethodName = s"$$object_methods$$${classFile.thisType.simpleName}:pc"
val (updatedClassFile, newMethodDescriptor) = createObjectMethodsTarget(
bootstrapMethod.arguments, name, newMethodName, classFile
).getOrElse {
instructions ++= dynamicLoad()
return (descriptor.computationalType.operandSize, classFile);
}
instructions ++= LoadMethodHandle_W(InvokeStaticMethodHandle(
classFile.thisType,
classFile.isInterfaceDeclaration,
newMethodName,
newMethodDescriptor
))
(1, updatedClassFile)
case _ =>
instructions ++= dynamicLoad()
(descriptor.computationalType.operandSize, classFile)
}
}
/**
* Generate Instructions to get a VarHandle for a field.
*/
private def fieldVarHandle(
fieldName: String,
declaringClass: BootstrapArgument,
loadFieldType: ClassFile => (Int, ClassFile),
isStaticField: Boolean,
instructions: InstructionsBuilder,
classFile: ClassFile
): (Int, ClassFile) = {
instructions ++= INVOKESTATIC( // Get suitable MethodHandles$Lookup object
ObjectType.MethodHandles,
isInterface = false,
"lookup",
MethodDescriptor.withNoArgs(ObjectType.MethodHandles$Lookup)
)
val (loadDeclClassStack, updatedClassFile1) =
loadClassType(declaringClass, instructions, classFile)
instructions ++= LoadString_W(fieldName)
val (loadFieldTypeStack, updatedClassFile2) = loadFieldType(updatedClassFile1)
instructions ++= INVOKEVIRTUAL(
ObjectType.MethodHandles$Lookup,
if (isStaticField) "findStaticVarHandle" else "findVarHandle",
MethodDescriptor.FindVarHandleDescriptor
)
(Math.max(1 + loadDeclClassStack, 3 + loadFieldTypeStack), updatedClassFile2)
}
/**
* Generate instructions to load a ClassObject from the given BootstrapArgument.
*/
private def loadClassType(
classType: BootstrapArgument,
instructions: InstructionsBuilder,
classFile: ClassFile
): (Int, ClassFile) = classType match {
case ConstantClass(tpe) =>
instructions ++= LoadClass_W(tpe)
(1, classFile)
case DynamicConstant(constantBSM, constantName, constantDescriptor) =>
loadDynamicConstant(
constantBSM, constantName, constantDescriptor, instructions, classFile
)
}
/**
* Generate instruction to load a Class object for the given FieldType.
*/
private def loadFieldType(fieldType: FieldType, instructions: InstructionsBuilder): Int = {
if (fieldType.isBaseType)
instructions ++= GETSTATIC(fieldType.asBaseType.WrapperType, "TYPE", ObjectType.Class)
else
instructions ++= LoadClass_W(fieldType.asReferenceType)
1
}
/**
* Generate instructions to read a static field from a VarHandle for a dynamically loaded class.
*/
private def doGetStatic(
classConstant: DynamicConstant,
name: String,
fieldType: FieldType,
instructions: InstructionsBuilder,
classFile: ClassFile,
boxed: Boolean
): (Int, ClassFile) = {
val (varHandleStack, updatedClassFile) = fieldVarHandle( // Get a suitable Varhandle
name,
classConstant,
cf => (loadFieldType(fieldType, instructions), cf),
isStaticField = true,
instructions,
classFile
)
instructions ++= ICONST_0 // The get method for a static VarHandle takes empty varargs
instructions ++= ANEWARRAY(ObjectType.Object)
instructions ++= INVOKEVIRTUAL(
ObjectType.VarHandle,
"get",
MethodDescriptor(ArrayType.ArrayOfObject, ObjectType.Object)
)
adaptReturnType(fieldType, boxed, instructions)
(Math.max(varHandleStack, 2), updatedClassFile)
}
/**
* Generate instructions to invoke a given MethodHandle.
*/
private def invokeMethodHandle(
methodHandle: ConstantValue[_],
arguments: ArraySeq[ConstantValue[_]],
returnType: FieldType,
instructions: InstructionsBuilder,
classFile: ClassFile,
boxed: Boolean
): (Int, ClassFile) = {
// Load the MethodHandle and keep track of its return type if we know it
val (actualReturnType, mhStack, newClassFile) = methodHandle match {
case mh: MethodCallMethodHandle =>
instructions ++= LoadMethodHandle_W(mh)
(Some(mh.methodDescriptor.returnType), 1, classFile)
case mh: FieldReadAccessMethodHandle =>
instructions ++= LoadMethodHandle_W(mh)
(Some(mh.fieldType), 1, classFile)
case mh: FieldWriteAccessMethodHandle =>
instructions ++= LoadMethodHandle_W(mh)
(Some(VoidType), 1, classFile)
case DynamicConstant(constantBSM, constantName, constantDescriptor) =>
val (loadConstantStack, newClassFile) = loadDynamicConstant(
constantBSM, constantName, constantDescriptor, instructions, classFile
)
(None, loadConstantStack, newClassFile)
}
var updatedClassFile = newClassFile
// If the return type is unknown or doesn't match the required, adapt it
val returnTypeStack = if (actualReturnType.isEmpty || actualReturnType.get != returnType) {
instructions ++= DUP
instructions ++= INVOKEVIRTUAL( // Get the full method type
ObjectType.MethodHandle,
"type",
MethodDescriptor.withNoArgs(ObjectType.MethodType)
)
val fieldTypeStack = loadFieldType(returnType, instructions) // Load the new return type
instructions ++= INVOKEVIRTUAL( // Adapt the method type
ObjectType.MethodType,
"changeReturnType",
MethodDescriptor(ObjectType.Class, ObjectType.MethodType)
)
instructions ++= INVOKEVIRTUAL( // Adapt the method handle
ObjectType.MethodHandle,
"asType",
MethodDescriptor(ObjectType.MethodType, ObjectType.MethodHandle)
)
1 + fieldTypeStack
} else 0
// Load the bootstrap arguments into a varargs array
instructions ++= LoadConstantInstruction(arguments.size)
instructions ++= ANEWARRAY(ObjectType.Object)
var argumentStack = 0
arguments.iterator.zipWithIndex foreach {
case (argument, index) =>
instructions ++= DUP
instructions ++= LoadConstantInstruction(index)
val (bsaStack, newClassFile) =
loadBootstrapArgument(argument, instructions, updatedClassFile, boxed = true)
updatedClassFile = newClassFile
instructions ++= AASTORE
argumentStack = Math.max(argumentStack, 2 + bsaStack)
}
instructions ++= INVOKEVIRTUAL( // Actually invoke the method handle
ObjectType.MethodHandle,
"invokeWithArguments",
MethodDescriptor(ArrayType.ArrayOfObject, ObjectType.Object)
)
adaptReturnType(returnType, boxed, instructions)
(Iterator(mhStack, 1 + returnTypeStack, 2 + argumentStack).max, updatedClassFile)
}
private def adaptReturnType(
returnType: FieldType,
boxed: Boolean,
instructions: InstructionsBuilder
): Unit = {
if (returnType.isBaseType) {
val wrapper = returnType.asBaseType.WrapperType
instructions ++= CHECKCAST(wrapper)
if (!boxed)
instructions ++= ObjectType.unboxValue(wrapper)
} else
instructions ++= CHECKCAST(returnType.asReferenceType)
}
/**
* Creates the code required to perform the action of a method created by
* java.lang.runtime.ObjectMethods.bootstrap.
*
* @param bootstrapArgs The arguments to the bootstrap method
* @param methodName Name of the represented method (either equals, toString or hashCode)
* @param newMethodName The name for the newly created method
* @return Either a tuple of the updated class file and the descriptor of the new method or None
*/
protected[this] def createObjectMethodsTarget(
bootstrapArgs: BootstrapArguments,
methodName: String,
newMethodName: String,
classFile: ClassFile
): Option[(ClassFile, MethodDescriptor)] = {
val recordType = bootstrapArgs.head match {
case ConstantClass(rt) => rt.asObjectType
case _ => return None;
}
val getters = bootstrapArgs.drop(2) map {
case mh: GetFieldMethodHandle => mh
case _ => return None;
}
/*
Make enough space for any of the methods. Exact sizes are (-5 if there is no getter):
- equals: 16 + getters.size * 28
- toString: 25 + getters.size * 20 + (getters.size - 1) * 6
- hashCode: 7 + getters.size * 18
*/
val body = new InstructionsBuilder(20 + getters.size * 28)
def prepareArgumentArray(hasTwoParameters: Boolean = false): Unit = {
body ++= ICONST_1
body ++= ANEWARRAY(ObjectType.Object)
body ++= (if (hasTwoParameters) ASTORE_2 else ASTORE_1)
}
// Loads a component (boxed), maxStack = 5
def loadComponent(
getter: MethodHandle,
hasTwoParameters: Boolean = false,
forSecondRecord: Boolean = false
): Unit = {
body ++= LoadConstantInstruction(getter, wide = true)
body ++= (if (hasTwoParameters) ALOAD_2 else ALOAD_1)
body ++= DUP
body ++= ICONST_0
body ++= (if (forSecondRecord) ALOAD_1 else ALOAD_0)
body ++= AASTORE
body ++= INVOKEVIRTUAL(
ObjectType.MethodHandle,
"invokeWithArguments",
MethodDescriptor(ArrayType.ArrayOfObject, ObjectType.Object)
)
}
val targetDescriptor = methodName match {
case "equals" =>
body ++= ALOAD_1 // if(!other instanceof recordType) return false;
body ++= INSTANCEOF(recordType)
body ++= IFNE(5)
body ++= ICONST_0
body ++= IRETURN
if (getters.nonEmpty)
prepareArgumentArray(hasTwoParameters = true)
getters.iterator.zipWithIndex foreach {
case (getter, index) =>
loadComponent(getter, hasTwoParameters = true)
loadComponent(getter, hasTwoParameters = true, forSecondRecord = true)
body ++= INVOKESTATIC(
ObjectType.Objects,
isInterface = false,
"equals",
MethodDescriptor(
ArraySeq(ObjectType.Object, ObjectType.Object),
BooleanType
)
)
// if(!this.component().equals(other.component()) return false;
body ++= IFEQ(-32 - index * 28)
}
body ++= ICONST_1 // return true;
body ++= IRETURN
MethodDescriptor(ArraySeq(recordType, ObjectType.Object), BooleanType)
case "toString" =>
val components = bootstrapArgs(1) match {
case ConstantString(cs) => cs.split(';')
case _ => return None;
}
def appendString(s: String): Unit = {
body ++= LoadString_W(s)
body ++= INVOKEVIRTUAL(
ObjectType.StringBuilder,
"append",
MethodDescriptor(ObjectType.String, ObjectType.StringBuilder)
)
}
body ++= NEW(ObjectType.StringBuilder)
body ++= DUP
body ++= LoadString_W(recordType.simpleName + '[')
body ++= INVOKESPECIAL(
ObjectType.StringBuilder,
isInterface = false,
"",
MethodDescriptor.JustTakes(ObjectType.String)
)
if (getters.nonEmpty)
prepareArgumentArray()
var first = true
components.zip(getters) foreach {
case (component, getter) =>
if (first) first = false
else appendString(", ")
appendString(component + '=')
loadComponent(getter)
body ++= INVOKEVIRTUAL(
ObjectType.StringBuilder,
"append",
MethodDescriptor(ObjectType.Object, ObjectType.StringBuilder)
)
}
body ++= LoadInt_W(']')
body ++= INVOKEVIRTUAL(
ObjectType.StringBuilder,
"append",
MethodDescriptor(CharType, ObjectType.StringBuilder)
)
body ++= INVOKEVIRTUAL(ObjectType.StringBuilder, "toString", JustReturnsString)
body ++= ARETURN
MethodDescriptor(recordType, ObjectType.String)
case "hashCode" =>
body ++= ICONST_0 // int hashCode = 0;
if (getters.nonEmpty)
prepareArgumentArray()
getters foreach { getter =>
body ++= BIPUSH(31) // hashCode = hashCode * 31 + component.hashCode();
body ++= IMUL
loadComponent(getter)
body ++= INVOKESTATIC(
ObjectType.Objects,
isInterface = false,
"hashCode",
MethodDescriptor.apply(ObjectType.Object, IntegerType)
)
body ++= IADD
}
body ++= IRETURN // return hashCode;
MethodDescriptor(recordType, IntegerType)
case _ =>
return None;
}
val maxStack = 6 // in all methods, the max stack is 1 value + the maxStack of loadComponent
val maxLocals = targetDescriptor.requiredRegisters + (if (getters.isEmpty) 0 else 1)
val attributes = if ("equals" == methodName) {
ArraySeq(StackMapTable(ArraySeq(SameFrame(7), SameFrame(1))))
} else {
ArraySeq.empty
}
val code = Code(maxStack, maxLocals, body.result(), NoExceptionHandlers, attributes)
// Access flags for the target method are `/* SYNTHETIC */ private static`
val accessFlags = ACC_SYNTHETIC.mask | ACC_PRIVATE.mask | ACC_STATIC.mask
val targetMethod = Method(accessFlags, newMethodName, targetDescriptor, ArraySeq(code))
val updatedClassFile = classFile._UNSAFE_addMethod(targetMethod)
Some((updatedClassFile, targetDescriptor))
}
/**
* Replaces each of several characters in a String with a given corresponding character.
*/
protected[this] def replaceChars(in: String, oldChars: String, newChars: String): String = {
var result = in
for ((oldC, newC) <- oldChars.zip(newChars)) {
result = result.replace(oldC, newC)
}
result
}
/**
* Generates a new, internal name for a method to be inserted.
*
* It follows the pattern:
* `$targetMethodName${surroundingMethodName}{surroundingMethodDescriptor}:pc`, where
* surroundingMethodDescriptor is the JVM descriptor of the method sanitized to not contain
* characters illegal in method names (replacing /, [, ;, < and > by $, ], :, _ and _
* respectively) and where pc is the pc of the invokedynamic that is rewritten.
*/
protected[this] def newTargetMethodName(
cp: Constant_Pool,
surroundingMethodNameIndex: Int,
surroundingMethodDescriptorIndex: Int,
pc: Int,
targetMethodName: String
): String = {
val methodName = cp(surroundingMethodNameIndex).asString match {
case "" => "$constructor$"
case "" => "$static_initializer"
case name => name
}
val descriptor = cp(surroundingMethodDescriptorIndex).asMethodDescriptor.toJVMDescriptor
val sanitizedDescriptor = replaceChars(descriptor, "/[;<>", "$]:__")
s"$$$targetMethodName$$$methodName$sanitizedDescriptor:$pc"
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy