
org.opalj.br.reader.DynamicConstantRewriting.scala Maven / Gradle / Ivy
The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br
package reader
import com.typesafe.config.Config
import com.typesafe.config.ConfigValueFactory
import org.opalj.log.OPALLogger.error
import org.opalj.log.OPALLogger.info
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.Instruction
import org.opalj.br.instructions.INVOKESTATIC
import org.opalj.br.instructions.LDC
import org.opalj.br.instructions.LDCDynamic
import org.opalj.br.instructions.LoadDynamic2_W
import org.opalj.br.instructions.LoadDynamic_W
import org.opalj.br.instructions.NOP
import org.opalj.br.instructions.ReturnInstruction
import scala.collection.immutable.ArraySeq
/**
* Provides support for rewriting Java 11 dynamic constant loading instructions.
* This trait should be mixed in alongside a [[BytecodeReaderAndBinding]], which extracts
* basic dynamic constant information from the [[BootstrapMethodTable]].
*
* Specifically, whenever an `ldc`, `ldc_w` or `ldc2_w` instruction is encountered that loads a
* dynamic constant, this trait tries to rewrite it to a simpler expression providing the same
* constant or an invocation of a method providing the constant from a lazily-initialized field.
*
* @note This rewriting is best-effort only. As `ldc` instructions take only two bytes, they cannot
* easily be rewritten to invocations, etc. `ldc_w` and `ldc2_w` instructions, however, are
* rewritten more aggressively.
*
* @see [[https://www.javacodegeeks.com/2018/08/hands-on-java-constantdynamic.html Hands on Java 11's constantdynamic]]
* for further information.
*
* @author Dominik Helm
*/
trait DynamicConstantRewriting
extends DeferredDynamicConstantResolution
with BootstrapArgumentLoading {
this: ClassFileBinding =>
import org.opalj.br.reader.DynamicConstantRewriting._
val performRewriting: Boolean = {
val rewrite: Boolean =
try {
config.getBoolean(RewritingConfigKey)
} catch {
case t: Throwable =>
error("class file reader", s"couldn't read: $RewritingConfigKey", t)
false
}
info(
"class file reader",
s"dynamic constants are ${if (rewrite) "" else "not "}rewritten"
)
rewrite
}
val logRewrites: Boolean = {
val logRewrites: Boolean =
try {
config.getBoolean(LogRewritingsConfigKey)
} catch {
case t: Throwable =>
error("class file reader", s"couldn't read: $LogRewritingsConfigKey", t)
false
}
info(
"class file reader",
s"rewrites of dynamic constants are ${if (logRewrites) "" else "not "}logged"
)
logRewrites
}
val logUnknownDynamicConstants: Boolean = {
val logUnknown: Boolean =
try {
config.getBoolean(LogUnknownDynamicConstantsConfigKey)
} catch {
case t: Throwable =>
error(
"class file reader",
s"couldn't read: $LogUnknownDynamicConstantsConfigKey", t
)
false
}
info(
"class file reader",
s"unknown dynamic constants are ${if (logUnknown) "" else "not "}logged"
)
logUnknown
}
val logUnresolvedDynamicConstants: Boolean = {
val logUnresolved: Boolean =
try {
config.getBoolean(LogUnresolvedDynamicConstantsConfigKey)
} catch {
case t: Throwable =>
error(
"class file reader",
s"couldn't read: $LogUnresolvedDynamicConstantsConfigKey", t
)
false
}
info(
"class file reader",
s"unresolved dynamic constants are ${if (logUnresolved) "" else "not "}logged"
)
logUnresolved
}
override def deferredDynamicConstantResolution(
classFile: ClassFile,
cp: Constant_Pool,
methodNameIndex: Constant_Pool_Index,
methodDescriptorIndex: Constant_Pool_Index,
dynamicInfo: CONSTANT_Dynamic_info,
instructions: Array[Instruction],
pc: PC
): ClassFile = {
// gather complete information about ldc/ldc(2)_w instruction from bootstrap method table
var updatedClassFile =
super.deferredDynamicConstantResolution(
classFile,
cp,
methodNameIndex,
methodDescriptorIndex,
dynamicInfo,
instructions,
pc
)
if (!performRewriting)
return updatedClassFile;
val load = instructions(pc)
val LDCDynamic(bootstrapMethod, name, descriptor) = load
val instructionLength = if (load.opcode == LDC.opcode) 2 else 3
// Generate instructions to load the constant and add matching return
val instructionsBuilder = new InstructionsBuilder(3)
val (maxStack, newClassFile) =
loadDynamicConstant(bootstrapMethod, name, descriptor, instructionsBuilder, classFile)
updatedClassFile = newClassFile
instructionsBuilder ++= ReturnInstruction(descriptor)
val newInstructions = instructionsBuilder.result()
val newLength = newInstructions.length - 1 // Don't count the return
val head = newInstructions.head
if (newLength == 3 && // There might not be an actual replacement
(head.isInstanceOf[LoadDynamic_W] || head.isInstanceOf[LoadDynamic2_W])) {
if (logUnknownDynamicConstants) {
val t = classFile.thisType.toJava
info(
"load-time transformation",
s"$t - unresolved ${load.mnemonic.toUpperCase}: $load"
)
}
} else if (newLength <= instructionLength) { // Short enough, use directly
var i = 0
while (i < instructionLength) {
instructions(pc + i) = if (i < newLength) newInstructions(i) else NOP
i += 1
}
if (logRewrites)
info("rewriting dynamic constant", s"Java: $load => $head")
} else if (instructionLength == 3) { // Replace ldc(2)_w with invocation
val newMethodName = newTargetMethodName(
cp, methodNameIndex, methodDescriptorIndex, pc, "load_dynamic_contstant"
)
val newMethod = Method(
ACC_SYNTHETIC.mask | ACC_PRIVATE.mask | ACC_STATIC.mask,
newMethodName,
MethodDescriptor.withNoArgs(descriptor),
ArraySeq(Code(maxStack, 0, newInstructions, NoExceptionHandlers, NoAttributes))
)
updatedClassFile = updatedClassFile._UNSAFE_addMethod(newMethod)
val newInvoke = INVOKESTATIC(
classFile.thisType,
isInterface = classFile.isInterfaceDeclaration,
newMethodName,
MethodDescriptor.withNoArgs(descriptor)
)
instructions(pc) = newInvoke
if (logRewrites)
info("rewriting dynamic constant", s"Java: $load => $newInvoke")
} else { // Can't replace ldc with invocation, it has only 2 bytes
if (logUnresolvedDynamicConstants) {
val t = updatedClassFile.thisType.toJava
info("load-time transformation", s"$t - unresolved LDC (not enough bytes): $load")
}
}
updatedClassFile
}
}
object DynamicConstantRewriting {
final val DynamicConstantKeyPrefix = {
ClassFileReaderConfiguration.ConfigKeyPrefix+"DynamicConstants."
}
final val RewritingConfigKey = DynamicConstantKeyPrefix+"rewrite"
final val LogRewritingsConfigKey = DynamicConstantKeyPrefix+"logRewrites"
final val LogUnknownDynamicConstantsConfigKey =
DynamicConstantKeyPrefix+"logUnknownDynamicConstants"
final val LogUnresolvedDynamicConstantsConfigKey =
DynamicConstantKeyPrefix+"logUnresolvedDynamicConstants"
/**
* Returns the default config where the settings for rewriting and logging rewrites are
* set to the specified values.
*/
def defaultConfig(rewrite: Boolean, logRewrites: Boolean): Config = {
BaseConfig.
withValue(RewritingConfigKey, ConfigValueFactory.fromAnyRef(rewrite)).
withValue(LogRewritingsConfigKey, ConfigValueFactory.fromAnyRef(logRewrites))
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy