All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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