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

org.opalj.br.Code.scala Maven / Gradle / Ivy

The newest version!
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package br

import scala.annotation.tailrec
import scala.annotation.switch
import scala.reflect.ClassTag
import java.util.Arrays.fill
import scala.collection.AbstractIterator
import scala.collection.mutable
import scala.collection.immutable.IntMap
import scala.collection.immutable.Queue
import org.opalj.util.AnyToAnyThis
import org.opalj.bytecode.BytecodeProcessingFailedException
import org.opalj.collection.IntIterator
import org.opalj.collection.immutable.IntArraySet
import org.opalj.collection.immutable.IntTrieSet
import org.opalj.collection.immutable.IntTrieSet1
import org.opalj.collection.immutable.BitArraySet
import org.opalj.collection.immutable.IntIntPair
import org.opalj.collection.immutable.EmptyIntTrieSet
import org.opalj.collection.mutable.IntQueue
import org.opalj.collection.mutable.IntArrayStack
import org.opalj.br.cfg.CFGFactory
import org.opalj.br.cfg.CFG
import org.opalj.br.ClassHierarchy.PreInitializedClassHierarchy
import org.opalj.br.instructions._

/**
 * Representation of a method's code attribute, that is, representation of a method's
 * implementation.
 *
 * @param   maxStack The maximum size of the stack during the execution of the method.
 *          This value is determined by the compiler and is not necessarily the minimum.
 *          However, in the vast majority of cases it is the minimum.
 * @param   maxLocals The number of registers/local variables needed to execute the method.
 *          As in case of `maxStack` this number is expected to be the minimum, but this
 *          is not guaranteed.
 * @param   instructions The instructions of this `Code` array/`Code` block. Since the
 *          code array is not completely filled (it contains `null` values) the
 *          preferred way to iterate over all instructions is to use for-comprehensions
 *          and pattern matching or to use one of the predefined methods [[foreach]],
 *          [[collect]], [[collectPair]], [[collectWithIndex]], etc..
 *          The `instructions` array must not be mutated!
 *
 * @author Michael Eichberg
 */
final class Code private (
        val maxStack:          Int,
        val maxLocals:         Int,
        val instructions:      Array[Instruction],
        val exceptionHandlers: ExceptionHandlers,
        val attributes:        Attributes
) extends Attribute
    with CommonAttributes
    with InstructionsContainer
    with CodeSequence[Instruction]
    with Iterable[PCAndInstruction] {
    code =>

    def copy(
        maxStack:          Int                = this.maxStack,
        maxLocals:         Int                = this.maxLocals,
        instructions:      Array[Instruction] = this.instructions,
        exceptionHandlers: ExceptionHandlers  = this.exceptionHandlers,
        attributes:        Attributes         = this.attributes
    ): Code = {
        new Code(maxStack, maxLocals, instructions, exceptionHandlers, attributes)
    }

    override def similar(other: Attribute, config: SimilarityTestConfiguration): Boolean = {
        other match {
            case that: Code => this.similar(that, config)
            case _          => false
        }
    }

    def similar(other: Code, config: SimilarityTestConfiguration): Boolean = {

        if (!(this.maxStack == other.maxStack && this.maxLocals == other.maxLocals)) {
            return false;
        }
        if (this.exceptionHandlers != other.exceptionHandlers) {
            return false;
        }

        if (!(
            this.instructions.length == other.instructions.length && {
                var areEqual = true
                val max = instructions.length
                var i = 0
                while (i < max && areEqual) {
                    val thisI = this.instructions(i)
                    val otherI = other.instructions(i)
                    areEqual = (thisI == null && otherI == null) ||
                        (thisI != null && otherI != null && thisI.similar(otherI))
                    i += 1
                }
                areEqual
            }
        )) {
            return false;
        }

        compareAttributes(other.attributes, config).isEmpty
    }

    @inline final def codeSize: Int = instructions.length

    override def iterator: Iterator[PCAndInstruction] = {
        new AbstractIterator[PCAndInstruction] {
            private[this] var pc = 0

            def hasNext: Boolean = pc < instructions.length

            def next(): PCAndInstruction = {
                val inst = PCAndInstruction(pc, instructions(pc))
                pc = inst.instruction.indexOfNextInstruction(pc)(code)
                inst
            }
        }
    }

    def instructionIterator: Iterator[Instruction] = this.iterator.map(_.instruction)

    override def instructionsOption: Some[Array[Instruction]] = Some(instructions)

    /**
     * Returns an iterator to iterate over the program counters (`pcs`) of the instructions
     * of this `Code` block.
     *
     * @see See the method [[foreach]] for an alternative.
     */
    def programCounters: IntIterator = new IntIterator {
        private[this] var nextPC = 0 // there is always at least one instruction
        def hasNext: Boolean = nextPC < instructions.length
        def next(): Int = { val pc = nextPC; nextPC = pcOfNextInstruction(nextPC); pc }
    }

    def foreachProgramCounter[U](f: Int => U): Unit = {
        var nextPC = 0 // there is always at least one instruction
        val maxPC = instructions.length
        while (nextPC < maxPC) {
            f(nextPC)
            nextPC = pcOfNextInstruction(nextPC)
        }
    }

    /**
     * Counts the number of instructions.
     *
     * @note The number of instructions is always smaller or equal to the size of the code array.
     * @note This operation has complexity O(n).
     */
    def instructionsCount: Int = {
        var c = 0
        var pc = 0
        val max = instructions.length
        while (pc < max) {
            c += 1
            pc = pcOfNextInstruction(pc)
        }
        c
    }

    /**
     * Calculates for each instruction the subroutine to which it belongs to – if any.
     * This information is required to, e.g., identify the subroutine
     * contexts that need to be reset in case of an exception in a subroutine.
     *
     * @note   Calling this method only makes sense for Java bytecode that actually contains
     *         [[org.opalj.br.instructions.JSR]] and [[org.opalj.br.instructions.RET]]
     *         instructions.
     *
     * @return Basically a map that maps the `pc` of each instruction to the id of the
     *         subroutine.
     *         For each instruction (with a specific `pc`) the `pc` of the first instruction
     *         of the subroutine it belongs to is returned. The pc `0` identifies the instruction
     *         as belonging to the core method. The pc `-1` identifies the instruction as
     *         dead by compilation.
     */
    def belongsToSubroutine(): Array[Int] = {
        val subroutineIds = new Array[Int](instructions.length)
        fill(subroutineIds, -1) // <= initially all instructions belong to "no routine"

        val nextSubroutines = new IntQueue(0)

        def propagate(subroutineId: Int, subroutinePC: Int /*PC*/ ): Unit = {

            val nextPCs = new IntQueue(subroutinePC)
            while (nextPCs.nonEmpty) {
                val pc = nextPCs.dequeue
                if (subroutineIds(pc) == -1) {
                    subroutineIds(pc) = subroutineId
                    val instruction = instructions(pc)

                    (instruction.opcode: @switch) match {
                        case ATHROW.opcode                                    =>
                        /* Nothing do to; will be handled when we deal with exceptions. */

                        case /* xReturn: */ 176 | 175 | 174 | 172 | 173 | 177 =>
                        /* Nothing to do; there are no successor! */

                        case RET.opcode                                       =>
                        /*Nothing to do; handled by JSR*/
                        case JSR.opcode | JSR_W.opcode =>
                            val UnconditionalBranchInstruction(branchoffset) = instruction
                            nextSubroutines.enqueue(pc + branchoffset)
                            nextPCs.enqueue(pcOfNextInstruction(pc))

                        case GOTO.opcode | GOTO_W.opcode =>
                            val UnconditionalBranchInstruction(branchoffset) = instruction
                            nextPCs.enqueue(pc + branchoffset)

                        case /*IFs:*/ 165 | 166 | 198 | 199 |
                            159 | 160 | 161 | 162 | 163 | 164 |
                            153 | 154 | 155 | 156 | 157 | 158 =>
                            val SimpleConditionalBranchInstruction(branchoffset) = instruction
                            nextPCs.enqueue(pc + branchoffset)
                            nextPCs.enqueue(pcOfNextInstruction(pc))

                        case TABLESWITCH.opcode | LOOKUPSWITCH.opcode =>
                            val SwitchInstruction(defaultOffset, jumpOffsets) = instruction
                            nextPCs.enqueue(pc + defaultOffset)
                            jumpOffsets foreach { jumpOffset => nextPCs.enqueue(pc + jumpOffset) }

                        case _ =>
                            nextPCs.enqueue(pcOfNextInstruction(pc))
                    }
                }
            }
        }

        var remainingExceptionHandlers = exceptionHandlers

        while (nextSubroutines.nonEmpty) {
            val subroutineId = nextSubroutines.dequeue
            propagate(subroutineId, subroutineId)

            // all handlers that handle exceptions related to one of the instructions
            // belonging to this subroutine belong to this subroutine (unless the handler
            // is already associated with a previous subroutine!)
            def belongsToCurrentSubroutine(startPC: Int, endPC: Int, handlerPC: Int): Boolean = {
                var currentPC = startPC
                while (currentPC < endPC) {
                    if (subroutineIds(currentPC) != -1) {
                        propagate(subroutineId, handlerPC)
                        // we are done
                        return true;
                    } else {
                        currentPC = pcOfNextInstruction(currentPC)
                    }
                }
                false
            }

            remainingExceptionHandlers = remainingExceptionHandlers filter { eh =>
                subroutineIds(eh.handlerPC) == -1 && // we did not already analyze the handler
                    !belongsToCurrentSubroutine(eh.startPC, eh.endPC, eh.handlerPC)
            }
        }

        subroutineIds
    }

    /**
     * Returns the set of all program counters where two or more control flow
     * paths may join.
     *
     * ==Example==
     * {{{
     *     0: iload_1
     *     1: ifgt    6
     *     2: iconst_1
     *     5: goto 10
     *     6: ...
     *     9: iload_1
     *    10: return // <= PATH JOIN: the predecessors are the instructions 5 and 9.
     * }}}
     *
     * In case of exception handlers the sound overapproximation is made that
     * all exception handlers with a fitting type may be reached on multiple paths.
     */
    def cfJoins(
        implicit
        classHierarchy: ClassHierarchy = PreInitializedClassHierarchy
    ): IntTrieSet = {
        /* OLD - DOESN'T USE THE CLASS HIERARCHY!
        val instructions = this.instructions
        val instructionsLength = instructions.length
        val cfJoins = new mutable.BitSet(instructionsLength)
        exceptionHandlers.foreach { eh =>
            // [REFINE] For non-finally handlers, test if multiple paths
            // can lead to the respective exception
            cfJoins += eh.handlerPC
        }
        // The algorithm determines for each instruction the successor instruction
        // that is reached and then marks it. If an instruction was already reached in the
        // past, it will then mark the instruction as a "join" instruction.
        val isReached = new mutable.BitSet(instructionsLength)
        isReached += 0 // the first instruction is always reached!
        var pc = 0
        while (pc < instructionsLength) {
            val instruction = instructions(pc)
            val nextPC = pcOfNextInstruction(pc)
            @inline def runtimeSuccessor(pc: PC): Unit = {
                if (isReached.contains(pc))
                    cfJoins += pc
                else
                    isReached += pc
            }
            (instruction.opcode: @scala.annotation.switch) match {
                case ATHROW.opcode => /*already handled*/

                case RET.opcode    => /*Nothing to do; handled by JSR*/
                case JSR.opcode | JSR_W.opcode =>
                    val jsrInstr = instruction.asInstanceOf[JSRInstruction]
                    runtimeSuccessor(pc + jsrInstr.branchoffset)
                    runtimeSuccessor(nextPC)

                case GOTO.opcode | GOTO_W.opcode =>
                    val bInstr = instruction.asInstanceOf[UnconditionalBranchInstruction]
                    runtimeSuccessor(pc + bInstr.branchoffset)

                case /*IFs:*/ 165 | 166 | 198 | 199 |
                    159 | 160 | 161 | 162 | 163 | 164 |
                    153 | 154 | 155 | 156 | 157 | 158 =>
                    val bInstr = instruction.asInstanceOf[SimpleConditionalBranchInstruction]
                    val jumpTargetPC = pc + bInstr.branchoffset
                    if (jumpTargetPC != nextPC) {
                        // we have an "if" that always immediately continues with the next
                        // instruction; hence, this "if" is useless
                        runtimeSuccessor(jumpTargetPC)
                    }
                    runtimeSuccessor(nextPC)

                case TABLESWITCH.opcode | LOOKUPSWITCH.opcode =>
                    instruction.nextInstructions(pc)(code, null /*not required!*/ ) foreach { pc =>
                        runtimeSuccessor(pc)
                    }

                case /*xReturn:*/ 176 | 175 | 174 | 172 | 173 | 177 =>
                /*Nothing to do. (no successor!)*/

                case _ =>
                    runtimeSuccessor(nextPC)
            }
            pc = nextPC
        }
        cfJoins
        */
        val instructions = this.instructions
        val instructionsLength = instructions.length

        var cfJoins: IntTrieSet = EmptyIntTrieSet

        val isReached = new Array[Boolean](instructionsLength)
        isReached(0) = true // the first instruction is always reached!

        var pc = 0
        while (pc < instructionsLength) {
            val instruction = instructions(pc)
            val nextPC = pcOfNextInstruction(pc)

            @inline def runtimeSuccessor(pc: Int): Unit = {
                if (isReached(pc))
                    cfJoins +!= pc
                else
                    isReached(pc) = true
            }

            (instruction.opcode: @switch) match {
                case RET.opcode => // potential path joins are determined when we process JSRs

                case JSR.opcode | JSR_W.opcode =>
                    val UnconditionalBranchInstruction(branchoffset) = instruction
                    runtimeSuccessor(pc + branchoffset)
                    runtimeSuccessor(nextPC)

                case _ =>
                    val nextPCs = instruction.nextInstructions(pc)(this, classHierarchy)
                    nextPCs.foreach(runtimeSuccessor)
            }

            pc = nextPC
        }
        cfJoins
    }

    /**
     * Computes for each instruction the set of predecessor instructions as well as all
     * instructions without predecessors. Those instructions with multiple predecessors
     * are also returned.
     *
     * @return  (1) An array which contains for each instruction the set of all predecessors,
     *          (2) the set of all instructions which have only predecessors; i.e., no successors
     *          and (3) the set of all instructions where multiple paths join.
     *          ´(Array[PCs]/*PREDECESSOR_PCs*/, PCs/*FINAL_PCs*/, PCs/*CF_JOINS*/)´.
     *
     * Note, that in case of completely broken code, set 2 may contain other
     * instructions than `return` and `athrow` instructions.
     * If the code contains jsr/ret instructions, the full blown CFG is computed.
     */
    def predecessorPCs(implicit classHierarchy: ClassHierarchy): (Array[PCs], PCs, PCs) = {
        implicit val code = this

        val instructions = this.instructions
        val instructionsLength = instructions.length

        val allPredecessorPCs = new Array[PCs](instructionsLength)
        allPredecessorPCs(0) = EmptyIntTrieSet // initialization for the start node
        var exitPCs: IntTrieSet = EmptyIntTrieSet

        var cfJoins: IntTrieSet = EmptyIntTrieSet
        val isReached = new Array[Boolean](instructionsLength)
        isReached(0) = true // the first instruction is always reached!
        @inline def runtimeSuccessor(successorPC: Int): Unit = {
            if (isReached(successorPC))
                cfJoins +!= successorPC
            else
                isReached(successorPC) = true
        }

        lazy val cfg = CFGFactory(code, classHierarchy) // fallback if we analyze pre Java 5 code...
        var pc = 0
        while (pc < instructionsLength) {
            val i = instructions(pc)
            val nextPCs =
                if (i.opcode == RET.opcode)
                    i.asInstanceOf[RET].nextInstructions(pc, () => cfg)
                else
                    i.nextInstructions(pc, regularSuccessorsOnly = false)
            if (nextPCs.isEmpty) {
                exitPCs += pc
            } else {
                nextPCs foreach { nextPC =>
                    if (nextPC < instructionsLength) {
                        // compute cfJoins
                        runtimeSuccessor(nextPC)
                        // compute predecessors
                        val predecessorPCs = allPredecessorPCs(nextPC)
                        if (predecessorPCs eq null) {
                            allPredecessorPCs(nextPC) = IntTrieSet1(pc)
                        } else {
                            allPredecessorPCs(nextPC) = predecessorPCs +! pc
                        }
                    } else {
                        // This handles cases where we have totally broken code; e.g.,
                        // compile-time dead code at the end of the method where the
                        // very last instruction is not even a ret/jsr/goto/return/atrow
                        // instruction (e.g., a NOP instruction as in case of jPython
                        // related classes.)
                        exitPCs +!= pc
                    }
                }
            }
            pc = i.indexOfNextInstruction(pc)
        }

        (allPredecessorPCs, exitPCs, cfJoins)
    }

    /**
     * Computes for each instruction which variables are live; see
     * `liveVariables(predecessorPCs: Array[PCs], finalPCs: PCs, cfJoins: BitSet)` for further
     * details.
     */
    def liveVariables(implicit classHierarchy: ClassHierarchy): LiveVariables = {
        val (predecessorPCs, finalPCs, cfJoins) = this.predecessorPCs(classHierarchy)
        liveVariables(predecessorPCs, finalPCs, cfJoins)
    }

    /**
     * Performs a live variable analysis restricted to a method's locals.
     *
     * @return  For each instruction (identified by its pc) the set of variables (register values)
     *          which are live (identified by their index) is determined.
     *          I.e., if you need to know if the variable with the index 5 is
     *          (still) live at instruction j with pc 37 it is sufficient to test if the bit
     *          set stored at index 37 contains the value 5.
     */
    def liveVariables(
        predecessorPCs: Array[PCs],
        finalPCs:       PCs,
        cfJoins:        PCs
    ): LiveVariables = {
        // IMPROVE Use StackMapTable (if available) to preinitialize the live variable information
        val instructions = this.instructions
        val instructionsLength = instructions.length
        val liveVariables = new Array[BitArraySet](instructionsLength)
        val workqueue = IntQueue.empty
        val AllDead = BitArraySet.empty
        finalPCs foreach { pc => liveVariables(pc) = AllDead; workqueue.enqueue(pc) }
        // required to handle endless loops!
        cfJoins foreach { pc =>
            val instruction = instructions(pc)
            var liveVariableInfo = AllDead
            if (instruction.readsLocal) {
                // This instruction is by construction "not a final instruction"
                // because this instruction never throws any(!) exceptions and it
                // also never "returns".
                liveVariableInfo += instruction.indexOfReadLocal
            }
            liveVariables(pc) = liveVariableInfo
            workqueue.enqueue(pc)
        }
        while (!workqueue.isEmpty) {
            val pc = workqueue.dequeue
            val instruction = instructions(pc)
            var liveVariableInfo = liveVariables(pc)
            if (instruction.readsLocal) {
                val lvIndex = instruction.indexOfReadLocal
                if (!liveVariableInfo.contains(lvIndex)) {
                    liveVariableInfo += lvIndex
                    liveVariables(pc) = liveVariableInfo
                }
            } else if (instruction.writesLocal) {
                val lvIndex = instruction.indexOfWrittenLocal
                if (liveVariableInfo.contains(lvIndex)) {
                    liveVariableInfo -= lvIndex
                    liveVariables(pc) = liveVariableInfo
                }
            }
            val thePCPredecessorPCs = predecessorPCs(pc)
            // if the code contains some "trivially" dead code as, e.g., shown next:
            //      com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary (JDK8u92)
            //      PC   Line  Instruction
            //      0    1363  aload_0
            //      1    |     checkcast com.sun.org.apache.xalan.internal.xsltc.DOM
            //      4    |     areturn
            //      5    1365  astore_1  // ... exception handler for irrelevant exception...
            //      ...
            //      23   |     areturn
            // predecessorPCs(pc) will be null!
            if (thePCPredecessorPCs ne null) {
                thePCPredecessorPCs foreach { predecessorPC =>
                    val predecessorLiveVariableInfo = liveVariables(predecessorPC)
                    if (predecessorLiveVariableInfo eq null) {
                        liveVariables(predecessorPC) = liveVariableInfo
                        workqueue.enqueue(predecessorPC)
                    } else {
                        val newLiveVariableInfo = predecessorLiveVariableInfo | liveVariableInfo
                        if (newLiveVariableInfo != predecessorLiveVariableInfo) {
                            liveVariables(predecessorPC) = newLiveVariableInfo
                            workqueue.enqueue(predecessorPC)
                        }
                    }
                }
            }
        }
        liveVariables
    }

    /**
     * Returns the set of all program counters where two or more control flow paths join or fork.
     *
     * ==Example==
     * {{{
     *  0: iload_1
     *  1: ifgt    6 // <= PATH FORK
     *  2: iconst_1
     *  5: goto 10
     *  6: ...
     *  9: iload_1
     * 10: return // <= PATH JOIN: the predecessors are the instructions 5 and 9.
     * }}}
     *
     * In case of exception handlers the sound overapproximation is made that
     * all exception handlers may be reached on multiple paths.
     *
     * @return A triple which contains (1) the set of pcs of those instructions where multiple
     *         control-flow paths join; (2) the pcs of the instructions which may result in
     *         multiple different control-flow paths and (3) for each of the later instructions
     *         the set of all potential targets.
     */
    def cfPCs(
        implicit
        classHierarchy: ClassHierarchy = PreInitializedClassHierarchy
    ): (PCs /*cfJoins*/ , PCs /*forks*/ , IntMap[PCs] /*forkTargetPCs*/ ) = {
        val instructions = this.instructions
        val instructionsLength = instructions.length

        var cfJoins: IntTrieSet = EmptyIntTrieSet
        var cfForks: IntTrieSet = EmptyIntTrieSet
        var cfForkTargets = IntMap.empty[IntTrieSet]

        val isReached = new Array[Boolean](instructionsLength)
        isReached(0) = true // the first instruction is always reached!

        lazy val cfg = CFGFactory(this, classHierarchy)

        var pc = 0
        while (pc < instructionsLength) {
            val instruction = instructions(pc)
            val nextPC = pcOfNextInstruction(pc)

            @inline def runtimeSuccessor(pc: Int): Unit = {
                if (isReached(pc))
                    cfJoins += pc
                else
                    isReached(pc) = true
            }

            (instruction.opcode: @switch) match {
                case RET.opcode =>
                    // The ret may return to different sites;
                    // the potential path joins are determined when we process the JSR.
                    cfForks +!= pc
                    cfForkTargets += ((pc, cfg.successors(pc)))

                case JSR.opcode | JSR_W.opcode =>
                    val jsrInstr = instruction.asInstanceOf[JSRInstruction]
                    runtimeSuccessor(pc + jsrInstr.branchoffset)
                    runtimeSuccessor(nextPC)

                case _ =>
                    val nextInstructions = instruction.nextInstructions(pc)(this, classHierarchy)
                    nextInstructions.foreach(runtimeSuccessor)
                    if (nextInstructions.length > 1) {
                        cfForks +!= pc
                        cfForkTargets += ((pc, nextInstructions.foldLeft(IntTrieSet.empty)(_ +! _)))
                    }
            }

            pc = nextPC
        }
        (cfJoins, cfForks, cfForkTargets)
    }

    /**
     * Iterates over all instructions and calls the given function `f` for every instruction.
     */
    @inline final def iterate[U](f: ( /*pc:*/ Int, Instruction) => U): Unit = {
        val instructionsLength = instructions.length
        var pc = 0
        while (pc < instructionsLength) {
            val instruction = instructions(pc)
            f(pc, instruction)
            pc = pcOfNextInstruction(pc)
        }
    }

    /**
     * Iterates over all instructions with the given opcode and calls the given function `f` for every instruction.
     */
    @inline final def iterate[U](
        instructionType: InstructionMetaInformation
    )(
        f: ( /*pc:*/ Int, Instruction) => U
    ): Unit = {
        val opcode = instructionType.opcode
        val instructionsLength = instructions.length
        var pc = 0
        while (pc < instructionsLength) {
            val instruction = instructions(pc)
            if (instruction.opcode == opcode) f(pc, instruction)
            pc = pcOfNextInstruction(pc)
        }
    }

    @inline final def forall(f: ( /*pc:*/ Int, Instruction) => Boolean): Boolean = {
        val instructionsLength = instructions.length
        var pc = 0
        while (pc < instructionsLength) {
            val instruction = instructions(pc)
            if (!f(pc, instruction))
                return false;

            pc = pcOfNextInstruction(pc)
        }
        true
    }

    /**
     * Iterates over all instructions and calls the given function `f`
     * for every instruction.
     */
    @inline final def foreachInstruction[U](f: Instruction => U): Unit = {
        val instructionsLength = instructions.length
        var pc = 0
        while (pc < instructionsLength) {
            val instruction = instructions(pc)
            f(instruction)
            pc = pcOfNextInstruction(pc)
        }
    }

    /**
     * Iterates over all instructions and calls the given function `f`
     * for every instruction.
     */
    @inline final def foreachPC[U](f: PC => U): Unit = {
        val instructionsLength = instructions.length
        var pc = 0
        while (pc < instructionsLength) {
            f(pc)
            pc = pcOfNextInstruction(pc)
        }
    }

    /**
     * Returns a view of all handlers (exception and finally handlers) for the
     * instruction with the given program counter (`pc`) that may catch an exception; as soon
     * as a finally handler is found no further handlers will be returned!
     *
     * In case of multiple exception handlers that are identical (in particular
     * in case of the finally handlers) only the first one is returned as that
     * one is the one that will be used by the JVM at runtime.
     * No further checks (w.r.t. the type hierarchy) are done.
     *
     * @param pc The program counter of an instruction of this `Code` array.
     */
    def handlersFor(pc: Int, justExceptions: Boolean = false): List[ExceptionHandler] = {
        var handledExceptions = Set.empty[ObjectType]
        val ehs = List.newBuilder[ExceptionHandler]
        exceptionHandlers forall { eh =>
            if (eh.startPC <= pc && eh.endPC > pc) {
                val catchTypeOption = eh.catchType
                if (catchTypeOption.isDefined) {
                    val catchType = catchTypeOption.get
                    if (!handledExceptions.contains(catchType)) {
                        handledExceptions += catchType
                        ehs += eh
                    }
                    true
                } else {
                    if (!justExceptions) {
                        ehs += eh
                    }
                    false
                }

            } else {
                // the handler is not relevant
                true
            }
        }
        ehs.result()
    }

    /**
     * Returns a view of all potential exception handlers (if any) for the
     * instruction with the given program counter (`pc`). `Finally` handlers
     * (`catchType == None`) are not returned but will stop the evaluation (as all further
     * exception handlers have no further meaning w.r.t. the runtime)!
     * In case of identical caught exceptions only the
     * first of them will be returned. No further checks (w.r.t. the typehierarchy) are done.
     *
     * @param pc The program counter of an instruction of this `Code` array.
     */
    def exceptionHandlersFor(pc: PC): List[ExceptionHandler] = {
        handlersFor(pc, justExceptions = true)
    }

    /**
     * Returns the handlers that may handle the given exception.
     *
     * The (known/given) type hierarchy is taken into account as well as
     * the order between the exception handlers.
     */
    def handlersForException(
        pc:        Int,
        exception: ObjectType
    )(
        implicit
        classHierarchy: ClassHierarchy = ClassHierarchy.PreInitializedClassHierarchy
    ): List[ExceptionHandler] = {
        import classHierarchy.isASubtypeOf

        var handledExceptions = Set.empty[ObjectType]

        val ehs = List.newBuilder[ExceptionHandler]
        exceptionHandlers forall { eh =>
            if (eh.startPC <= pc && eh.endPC > pc) {
                val catchTypeOption = eh.catchType
                if (catchTypeOption.isDefined) {
                    val catchType = catchTypeOption.get
                    val isSubtype = isASubtypeOf(exception, catchType)
                    if (isSubtype.isYes) {
                        ehs += eh
                        /* we found a definitiv matching handler*/ false
                    } else if (isSubtype.isUnknown) {
                        if (!handledExceptions.contains(catchType)) {
                            handledExceptions += catchType
                            ehs += eh
                        }
                        /* we may have a better fit */ true
                    } else {
                        /* the exception type is not relevant*/ true
                    }
                } else {
                    ehs += eh
                    /* we are done; we found a finally handler... */ false
                }
            } else {
                /* the handler is not relevant */ true
            }
        }
        ehs.result()
    }

    /**
     * The list of pcs of those instructions that may handle an exception if the evaluation
     * of the instruction with the given `pc` throws an exception.
     *
     * In case of multiple finally handlers only the first one will be returned and no further
     * exception handlers will be returned. In case of identical caught exceptions only the
     * first of them will be returned. No further checks (w.r.t. the type hierarchy) are done.
     *
     * If different exceptions are handled by the same handler, the corresponding pc is returned
     * multiple times.
     */
    def handlerInstructionsFor(pc: Int): List[Int] /*Chain[PC]*/ = {
        var handledExceptions = Set.empty[ObjectType]

        val pcs = List.newBuilder[Int] /*PC*/
        exceptionHandlers forall { eh =>
            if (eh.startPC <= pc && eh.endPC > pc) {
                val catchTypeOption = eh.catchType
                if (catchTypeOption.isDefined) {
                    val catchType = catchTypeOption.get
                    if (!handledExceptions.contains(catchType)) {
                        handledExceptions += catchType
                        pcs += eh.handlerPC
                    }
                    true
                } else {
                    pcs += eh.handlerPC
                    false // we effectively abort after the first finally handler
                }
            } else {
                // the handler is not relevant
                true
            }
        }
        pcs.result()
    }

    /**
     * Returns the program counter of the next instruction after the instruction with
     * the given counter (`currentPC`).
     *
     * @param  currentPC The program counter of an instruction. If `currentPC` is the
     *                   program counter of the last instruction of the code block then the returned
     *                   program counter will be equivalent to the length of the Code/Instructions
     *                   array.
     */
    @inline final def pcOfNextInstruction(currentPC: Int): /*PC*/ Int = {
        instructions(currentPC).indexOfNextInstruction(currentPC)(this)
        // OLD: ITERATING OVER THE ARRAY AND CHECKING FOR NON-NULL IS NO LONGER SUPPORTED!
        //    @inline final def pcOfNextInstruction(currentPC: PC): PC = {
        //        val max_pc = instructions.size
        //        var nextPC = currentPC + 1
        //        while (nextPC < max_pc && (instructions(nextPC) eq null))
        //            nextPC += 1
        //
        //        nextPC
        //    }
    }

    /**
     * Returns the program counter of the previous instruction in the code array.
     * `currentPC` must be the program counter of an instruction.
     *
     * This function is only defined if currentPC is larger than 0; i.e., if there
     * is a previous instruction! If currentPC is larger than `instructions.size` the
     * behavior is undefined.
     */
    @inline final def pcOfPreviousInstruction(currentPC: Int): /*PC*/ Int = {
        var previousPC = currentPC - 1
        val instructions = this.instructions
        while (previousPC > 0 && !instructions(previousPC).isInstanceOf[Instruction]) {
            previousPC -= 1
        }
        previousPC
    }

    /**
     * Returns the line number table - if any.
     *
     * @note    A code attribute is allowed to have multiple line number tables. However, all
     *          tables are merged into one by OPAL at class loading time.
     *
     * @note    Depending on the configuration of the reader for `ClassFile`s this
     *          attribute may not be reified.
     */
    def lineNumberTable: Option[LineNumberTable] = {
        attributes collectFirst { case lnt: LineNumberTable => lnt }
    }

    /**
     * Returns the line number associated with the instruction with the given pc if
     * it is available.
     *
     * @param pc Index of the instruction for which we want to get the line number.
     * @return `Some` line number or `None` if no line-number information is available.
     */
    def lineNumber(pc: Int): Option[Int] = lineNumberTable.flatMap(_.lookupLineNumber(pc))

    /**
     * Returns `Some(true)` if both pcs have the same line number. If line number information
     * is not available `None` is returned.
     */
    def haveSameLineNumber(firstPC: Int, secondPC: Int): Option[Boolean] = {
        lineNumber(firstPC).flatMap(firstLN => lineNumber(secondPC).map(_ == firstLN))
    }

    /**
     * Returns the smallest line number (if any).
     *
     * @note   The line number associated with the first instruction (pc === 0) is
     *         not necessarily the smallest one.
     *         {{{
     *                  public void foo(int i) {
     *                    super.foo( // The call has the smallest line number.
     *                      i+=1; // THIS IS THE FIRST OPERATION...
     *                    )
     *                  }
     *         }}}
     */
    def firstLineNumber: Option[Int] = lineNumberTable.flatMap(_.firstLineNumber())

    /**
     * Collects (the merged if necessary) local variable table.
     *
     * @note   A code attribute is allowed to have multiple local variable tables. However, all
     *         tables are merged into one by OPAL at class loading time.
     *
     * @note   Depending on the configuration of the reader for `ClassFile`s this
     *         attribute may not be reified.
     */
    def localVariableTable: Option[LocalVariables] = {
        attributes collectFirst { case LocalVariableTable(lvt) => lvt }
    }

    /**
     * Returns the set of local variables defined at the given pc base on debug information.
     *
     * @return A mapping of the index to the name of the local variable. The map is
     *         empty if no debug information is available.
     */
    def localVariablesAt(pc: Int): Map[Int, LocalVariable] = { // IMRPOVE Use IntMap for the return value.
        localVariableTable match {
            case Some(lvt) =>
                lvt.collect {
                    case lv @ LocalVariable(
                        startPC,
                        length,
                        _ /*name*/ ,
                        _ /*fieldType*/ ,
                        index
                        ) if startPC <= pc && startPC + length > pc =>
                        (index, lv)
                }.toMap
            case _ =>
                Map.empty
        }
    }

    /**
     * Returns the local variable stored at the given local variable index that is live at
     * the given instruction (pc).
     */
    def localVariable(pc: Int, index: Int): Option[LocalVariable] = {
        localVariableTable flatMap { lvs =>
            lvs find { lv =>
                val result = lv.index == index &&
                    lv.startPC <= pc &&
                    (lv.startPC + lv.length) > pc
                result
            }
        }
    }

    /**
     * Collects all local variable type tables.
     *
     * @note Depending on the configuration of the reader for `ClassFile`s this
     *       attribute may not be reified.
     */
    def localVariableTypeTable: Iterable[LocalVariableTypes] = {
        attributes collect { case LocalVariableTypeTable(lvtt) => lvtt }
    }

    /**
     * The JVM specification mandates that a Code attribute has at most one
     * StackMapTable attribute.
     *
     * @note   Depending on the configuration of the reader for `ClassFile`s this
     *         attribute may not be reified.
     */
    def stackMapTable: Option[StackMapTable] = {
        attributes collectFirst { case smt: StackMapTable => smt }
    }

    /**
     * Computes the set of PCs for which a stack map frame is required. Calling this method
     * (i.e., the generation of stack map tables in general) is only defined for Java > 5 code;
     * i.e., cocde which does not use JSR/RET; therefore the behavior for Java 5 or earlier code
     * is deliberately undefined.
     *
     * @param classHierarchy The computation of the stack map table generally requires the
     *                       presence of a complete type hierarchy.
     *
     * @return The sorted set of PCs for which a stack map frame is required.
     */
    def stackMapTablePCs(implicit classHierarchy: ClassHierarchy): IntArraySet = {

        var stackMapTablePCs: IntArraySet = IntArraySet.empty
        iterate { (pc, instruction) =>
            if (instruction.isControlTransferInstruction) {
                instruction.opcode match {
                    case JSR.opcode | JSR_W.opcode | RET.opcode =>
                        throw BytecodeProcessingFailedException(
                            "computation of stack map tables containing JSR/RET is not supported; "+
                                "the attribute is neither required nor helpful in this case"
                        )
                    case GOTO.opcode | GOTO_W.opcode =>
                        stackMapTablePCs += pc + instruction.asGotoInstruction.branchoffset
                        val nextPC = code.pcOfNextInstruction(pc)
                        if (nextPC < codeSize) {
                            // test for a goto at the end...
                            stackMapTablePCs += nextPC
                        }
                    case _ =>
                        stackMapTablePCs ++=
                            instruction.asControlTransferInstruction.jumpTargets(pc)(
                                code = this,
                                classHierarchy = classHierarchy
                            )
                }
            }
        }

        code.exceptionHandlers.foreach(ex => stackMapTablePCs += ex.handlerPC)

        stackMapTablePCs
    }

    /**
     * True if the instruction with the given program counter is modified by wide.
     *
     * @param pc A valid index in the code array.
     */
    @inline def isModifiedByWide(pc: Int): Boolean = pc > 0 && instructions(pc - 1) == WIDE

    def foldLeft[T <: Any](start: T)(f: (T, Int /*PC*/ , Instruction) => T): T = {
        val max_pc = instructions.length
        var pc = 0
        var vs = start
        while (pc < max_pc) {
            vs = f(vs, pc, instructions(pc))
            pc = pcOfNextInstruction(pc)
        }
        vs
    }

    /**
     * Collects all instructions for which the given function is defined. The order in
     * which the instructions are collected is reversed when compared to the order in the
     * instructions array.
     */
    def collectInstructions[B <: AnyRef](f: PartialFunction[Instruction, B]): List[B] = {
        val max_pc = instructions.length
        var result: List[B] = List.empty
        var pc = 0
        while (pc < max_pc) {
            val instruction = instructions(pc)
            val r: Any = f.applyOrElse(instruction, AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                result ::= r.asInstanceOf[B]
            }
            pc = pcOfNextInstruction(pc)
        }
        result
    }

    /**
     * Collects all instructions for which the given function is defined.
     */
    def collectInstructionsWithPC[B <: AnyRef](
        f: PartialFunction[PCAndInstruction, B]
    ): List[PCAndAnyRef[B]] = {
        val max_pc = instructions.length
        var result: List[PCAndAnyRef[B]] = List.empty
        var pc = 0
        while (pc < max_pc) {
            val instruction = instructions(pc)
            val r: Any = f.applyOrElse(PCAndInstruction(pc, instruction), AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                result ::= PCAndAnyRef(pc, r.asInstanceOf[B])
            }
            pc = pcOfNextInstruction(pc)
        }
        result
    }

    /**
     * Collects all instructions for which the given function is defined.
     *
     * ==Usage scenario==
     * Use this function if you want to search for and collect specific instructions and
     * when you do not immediately require the program counter/index of the instruction
     * in the instruction array to make the decision whether you want to collect the
     * instruction.
     *
     * ==Examples==
     * Example usage to collect the declaring class of all get field accesses where the
     * field name is "last".
     * {{{
     * collect({
     *  case GETFIELD(declaringClass, "last", _) => declaringClass
     * })
     * }}}
     *
     * Example usage to collect all instances of a "DUP" instruction.
     * {{{
     * code.collect({ case dup @ DUP => dup })
     * }}}
     *
     * @return The result of applying the function f to all instructions for which f is
     *         defined combined with the index (program counter) of the instruction in the
     *         code array.
     */
    def collect[B <: AnyRef](f: PartialFunction[Instruction, B]): List[PCAndAnyRef[B]] = {
        val max_pc = instructions.length
        var pc = 0
        var result: List[PCAndAnyRef[B]] = List.empty
        while (pc < max_pc) {
            val instruction = instructions(pc)
            val r: Any = f.applyOrElse(instruction, AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                result ::= PCAndAnyRef(pc, r.asInstanceOf[B])
            }
            pc = pcOfNextInstruction(pc)
        }
        result.reverse
    }

    def filter[B](f: (PC, Instruction) => Boolean): IntArraySet = {
        val max_pc = instructions.length

        val pcs = IntArrayStack.empty
        var pc = 0
        while (pc < max_pc) {
            if (f(pc, instructions(pc))) pcs += pc
            pc = pcOfNextInstruction(pc)
        }
        IntArraySet._UNSAFE_fromSorted(pcs.toArray)
    }

    /**
     * Finds a pair of consecutive instructions that are matched by the given partial
     * function.
     *
     * ==Example Usage==
     * {{{
     * (pc, _) <- body.findPair {
     *      case (
     *          INVOKESPECIAL(receiver1, _, SingleArgumentMethodDescriptor((paramType: BaseType, _))),
     *          INVOKEVIRTUAL(receiver2, name, NoArgumentMethodDescriptor(returnType: BaseType))
     *      ) if (...) => (...)
     *      } yield ...
     * }}}
     */
    def collectPair[B <: AnyRef](
        f: PartialFunction[(Instruction, Instruction), B]
    ): List[PCAndAnyRef[B]] = {
        val max_pc = instructions.length

        var firstPC = 0
        var firstInstruction = instructions(firstPC)
        var secondPC = pcOfNextInstruction(0)
        var secondInstruction: Instruction = null

        var result: List[PCAndAnyRef[B]] = Nil
        while (secondPC < max_pc) {
            secondInstruction = instructions(secondPC)

            val instrs = (firstInstruction, secondInstruction)
            val r: Any = f.applyOrElse(instrs, AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                result ::= PCAndAnyRef(firstPC, r.asInstanceOf[B])
            }

            firstInstruction = secondInstruction
            firstPC = secondPC
            secondPC = pcOfNextInstruction(secondPC)
        }
        result
    }

    /**
     * Finds a sequence of instructions that are matched by the given partial function.
     *
     * @note If possible, use one of the more specialized methods, such as, [[collectPair]].
     *       The pure iteration overhead caused by this method is roughly 10-20 times higher
     *       than this one.
     *
     * @return List of pairs where the first element is the pc of the first instruction
     *         of a matched sequence and the second value is the result of the evaluation
     *         of the partial function.
     */
    def findSequence[B <: AnyRef](
        windowSize: Int
    )(
        f: PartialFunction[Queue[Instruction], B]
    ): List[PCAndAnyRef[B]] = {
        require(windowSize > 0)

        val max_pc = instructions.length
        var instrs: Queue[Instruction] = Queue.empty
        var firstPC, lastPC = 0
        var elementsInQueue = 0

        //
        // INITIALIZATION
        //
        while (elementsInQueue < windowSize - 1 && lastPC < max_pc) {
            instrs = instrs.enqueue(instructions(lastPC))
            lastPC = pcOfNextInstruction(lastPC)
            elementsInQueue += 1
        }

        //
        // SLIDING OVER THE CODE
        //
        var result: List[PCAndAnyRef[B]] = Nil
        while (lastPC < max_pc) {
            instrs = instrs.enqueue(instructions(lastPC))

            val r: Any = f.applyOrElse(instrs, AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                result ::= PCAndAnyRef(firstPC, r.asInstanceOf[B])
            }

            firstPC = pcOfNextInstruction(firstPC)
            lastPC = pcOfNextInstruction(lastPC)
            instrs = instrs.tail
        }

        result.reverse
    }

    /**
     * Matches pairs of two consecutive instructions. For each matched pair,
     * the program counter of the first instruction is returned.
     *
     * ==Example Usage==
     * {{{
     * for {
     *  classFile <- project.view.map(_._1).par
     *  method @ MethodWithBody(body) <- classFile.methods
     *  pc <- body.matchPair({
     *      case (
     *          INVOKESPECIAL(receiver1, _, TheArgument(parameterType: BaseType)),
     *          INVOKEVIRTUAL(receiver2, name, NoArgumentMethodDescriptor(returnType: BaseType))
     *      ) => { (receiver1 eq receiver2) && (returnType ne parameterType) }
     *      case _ => false
     *      })
     *  } yield (classFile, method, pc)
     * }}}
     */
    def matchPair(f: (Instruction, Instruction) => Boolean): List[Int /*PC*/ ] = {
        val max_pc = instructions.length
        var pc1 = 0
        var pc2 = pcOfNextInstruction(pc1)

        var result: List[Int /*PC*/ ] = List.empty
        while (pc2 < max_pc) {
            if (f(instructions(pc1), instructions(pc2))) {
                result = pc1 :: result
            }

            pc1 = pc2
            pc2 = pcOfNextInstruction(pc2)
        }
        result
    }

    /**
     * Finds all sequences of three consecutive instructions that are matched by `f`.
     */
    def matchTriple(f: (Instruction, Instruction, Instruction) => Boolean): List[Int /*PC*/ ] = {
        matchTriple(Int.MaxValue, f)
    }

    /**
     * Finds a sequence of 3 consecutive instructions for which the given function returns
     * `true`, and returns the `PC` of the first instruction in each found sequence.
     *
     * @param matchMaxTriples Is the maximum number of triples that is passed to `f`.
     *      E.g., if `matchMaxTriples` is "1" only the first three instructions are
     *                        passed to `f`.
     */
    def matchTriple(
        matchMaxTriples: Int                                                = Int.MaxValue,
        f:               (Instruction, Instruction, Instruction) => Boolean
    ): List[Int /*PC*/ ] = {
        val max_pc = instructions.length
        var matchedTriplesCount = 0
        var pc1 = 0
        var pc2 = pcOfNextInstruction(pc1)
        if (pc2 >= max_pc)
            return List.empty;

        var pc3 = pcOfNextInstruction(pc2)

        var result: List[Int /*PC*/ ] = List.empty
        while (pc3 < max_pc && matchedTriplesCount < matchMaxTriples) {
            if (f(instructions(pc1), instructions(pc2), instructions(pc3))) {
                result = pc1 :: result
            }

            matchedTriplesCount += 1

            // Move forward by 1 instruction at a time. Even though (..., 1, 2, 3, _, ...)
            // didn't match, it's possible that (..., _, 1, 2, 3, ...) matches.
            pc1 = pc2
            pc2 = pc3
            pc3 = pcOfNextInstruction(pc3)
        }
        result
    }

    /**
     * Returns the next instruction that will be executed at runtime that is not a
     * [[org.opalj.br.instructions.GotoInstruction]].
     * If the given instruction is not a [[org.opalj.br.instructions.GotoInstruction]],
     * the given instruction is returned.
     */
    @tailrec def nextNonGotoInstruction(pc: Int): /*PC*/ Int = {
        instructions(pc) match {
            case GotoInstruction(branchoffset) => nextNonGotoInstruction(pc + branchoffset)
            case _                             => pc
        }
    }

    /**
     * Tests if the straight-line sequence of instructions that starts with the given `pc`
     * always ends with an `ATHROW` instruction or a method call that always throws an
     * exception. The call sequence furthermore has to contain no complex logic.
     * Here, complex means that evaluating the instruction may result in multiple control flows.
     * If the sequence contains complex logic, `false` will be returned.
     *
     * One use case of this method is, e.g., to check if the code
     * of the default case of a switch instruction always throws some error
     * (e.g., an `UnknownError` or `AssertionError`).
     * {{{
     * switch(...) {
     *  case X : ....
     *  default :
     *      throw new AssertionError();
     * }
     * }}}
     * This is a typical idiom used in Java programs and which may be relevant for
     * certain analyses to detect.
     *
     * @note   If complex control flows should also be considered it is possible to compute
     *         a methods [[org.opalj.br.cfg.CFG]] and use that one.
     *
     * @param  pc           The program counter of an instruction that strictly dominates all
     *                      succeeding instructions up until the next instruction (as determined
     *                      by [[#cfJoins]] where two or more paths join. If the pc belongs to an instruction
     *                      where multiple paths join, `false` will be returned.
     *
     * @param  anInvocation When the analysis finds a method call, it calls this method
     *                      to let the caller decide whether the called method is an (indirect) way
     *                      of always throwing an exception.
     *                      If `true` is returned the analysis terminates and returns `true`; otherwise
     *                      the analysis continues.
     *
     * @param  aThrow       If all (non-exception) paths will always end in one specific
     *                      `ATHROW` instruction then this function is called (callback) to let the
     *                      caller decide if the "expected" exception is thrown. This analysis will
     *                      return with the result of this call.
     *
     * @return `true` if the bytecode sequence starting with the instruction with the
     *         given `pc` always ends with an [[org.opalj.br.instructions.ATHROW]] instruction.
     *         `false` in all other cases (i.e., the sequence does not end with an `athrow`
     *         instruction or the control flow is more complex.)
     */
    @inline def alwaysResultsInException(
        pc:           Int,
        cfJoins:      IntTrieSet,
        anInvocation: ( /*PC*/ Int) => Boolean,
        aThrow:       ( /*PC*/ Int) => Boolean
    ): Boolean = {

        var currentPC = pc
        while (!cfJoins.contains(currentPC)) {
            val instruction = instructions(currentPC)

            (instruction.opcode: @scala.annotation.switch) match {
                case ATHROW.opcode =>
                    val result = aThrow(currentPC)
                    return result;

                case RET.opcode | JSR.opcode | JSR_W.opcode =>
                    return false;

                case GOTO.opcode | GOTO_W.opcode =>
                    currentPC += instruction.asInstanceOf[GotoInstruction].branchoffset

                case /*IFs:*/ 165 | 166 | 198 | 199 |
                    159 | 160 | 161 | 162 | 163 | 164 |
                    153 | 154 | 155 | 156 | 157 | 158 =>
                    return false;

                case TABLESWITCH.opcode | LOOKUPSWITCH.opcode =>
                    return false;

                case /*xReturn:*/ 176 | 175 | 174 | 172 | 173 | 177 =>
                    return false;

                case INVOKEINTERFACE.opcode
                    | INVOKESPECIAL.opcode
                    | INVOKESTATIC.opcode
                    | INVOKEVIRTUAL.opcode =>
                    if (anInvocation(currentPC))
                        return true;

                    currentPC = pcOfNextInstruction(currentPC)

                case _ =>
                    currentPC = pcOfNextInstruction(currentPC)
            }
        }

        false
    }

    @throws[ClassFormatError]("if it is impossible to compute the maximum height of the stack")
    def stackDepthAt(
        atPC:           Int,
        classHierarchy: ClassHierarchy = ClassHierarchy.PreInitializedClassHierarchy
    ): Int = {
        stackDepthAt(atPC, CFGFactory(this, classHierarchy))
    }

    /**
     * Computes the stack depth for the instruction with the given pc (`atPC`).
     * I.e, computes the stack depth before executing the instruction! This function is intended
     * to be used if and only if the stack depth is only required for a single instruction; it
     * recomputes the stack depth for all instructions whenever the function is called.
     *
     * @note If the CFG is already available, it should be passed as the computation is
     *       potentially the most expensive part.
     *
     * @return the stack depth or -1 if the instruction is invalid/dead.
     */
    @throws[ClassFormatError]("if it is impossible to compute the maximum height of the stack")
    def stackDepthAt(atPC: Int, cfg: CFG[Instruction, Code]): Int = {
        var paths: List[( /*PC*/ Int, Int /*stackdepth before executing the instruction*/ )] = List.empty
        val visitedPCs = new mutable.BitSet(instructions.length)

        // We start with the first instruction and an empty stack.
        paths ::= ((0, 0))
        visitedPCs += 0

        // We have to make sure, that all exception handlers are evaluated for
        // max_stack, if an exception is caught, the stack size is always 1 -
        // containing the exception itself.
        for (exceptionHandler <- exceptionHandlers) {
            val handlerPC = exceptionHandler.handlerPC
            if (visitedPCs.add(handlerPC)) paths ::= ((handlerPC, 1))
        }

        while (paths.nonEmpty) {
            val (pc, initialStackDepth) = paths.head
            if (pc == atPC) {
                return initialStackDepth;
            }
            paths = paths.tail
            val newStackDepth = initialStackDepth + instructions(pc).stackSlotsChange
            cfg.foreachSuccessor(pc) { succPC =>
                if (visitedPCs.add(succPC)) {
                    paths ::= ((succPC, newStackDepth))
                }
            }
        }

        -1
    }

    /**
     * This attribute's kind id.
     */
    override def kindId: Int = Code.KindId

    /**
     * A complete representation of this code attribute (including instructions,
     * attributes, etc.).
     */
    override def toString: String = {
        s"Code_attribute(maxStack=$maxStack, maxLocals=$maxLocals, "+
            instructions.zipWithIndex.filter(_._1 ne null).map(_.swap).toString +
            exceptionHandlers.toString+","+
            attributes.toString+
            ")"
    }

    /**
     * Collects the results of the evaluation of the partial function until the partial function
     * is not defined.
     *
     * @return The program counter of the instruction for which the given partial function was
     *         not defined along with the list of previous results. '''The results are sorted in
     *         descending order w.r.t. the PC'''.
     */
    def collectUntil[B <: AnyRef](f: PartialFunction[PCAndInstruction, B]): PCAndAnyRef[List[B]] = {
        val max_pc = instructions.length
        var pc = 0
        var result: List[B] = List.empty
        while (pc < max_pc) {
            val r: Any = f.applyOrElse(PCAndInstruction(pc, instructions(pc)), AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                result = r.asInstanceOf[B] :: result
            } else {
                return PCAndAnyRef(pc, result);
            }
            pc = pcOfNextInstruction(pc)
        }
        PCAndAnyRef(pc, result)
    }

    /**
     * Applies the given function to the first instruction for which the given function
     * is defined.
     */
    def collectFirstWithIndex[B](f: PartialFunction[PCAndInstruction, B]): Option[B] = {
        val max_pc = instructions.length
        var pc = 0
        while (pc < max_pc) {
            val r: Any = f.applyOrElse(PCAndInstruction(pc, instructions(pc)), AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                return Some(r.asInstanceOf[B]);
            }
            pc = pcOfNextInstruction(pc)
        }

        None
    }

    /**
     * Applies the given function `f` to all instruction objects for which the function is
     * defined. The function is passed a tuple consisting of the current program
     * counter/index in the code array and the corresponding instruction.
     *
     * ==Example==
     * Example usage to collect the program counters (indexes) of all instructions that
     * are the target of a conditional branch instruction:
     * {{{
     * code.collectWithIndex({
     *  case (pc, cbi: ConditionalBranchInstruction) =>
     *      Seq(cbi.indexOfNextInstruction(pc, code), pc + cbi.branchoffset)
     *  }) // .flatten should equal (Seq(...))
     * }}}
     */
    def collectWithIndex[B: ClassTag](f: PartialFunction[PCAndInstruction, B]): List[B] = {
        val max_pc = instructions.length
        var pc = 0
        val vs = List.newBuilder[B]
        while (pc < max_pc) {
            val r: Any = f.applyOrElse(PCAndInstruction(pc, instructions(pc)), AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                vs += r.asInstanceOf[B]
            }
            pc = pcOfNextInstruction(pc)
        }
        vs.result()
    }

    /**
     * Slides over the code array and tries to apply the given function to each sequence
     * of instructions consisting of `windowSize` elements.
     *
     * ==Scenario==
     * If you want to search for specific patterns of bytecode instructions. Some "bug
     * patterns" are directly related to specific bytecode sequences and these patterns
     * can easily be identified using this method.
     *
     * ==Example==
     * Search for sequences of the bytecode instructions `PUTFIELD` and `ALOAD_O` in the
     * method's body and return the list of program counters of the start of the
     * identified sequences.
     * {{{
     * code.slidingCollect(2)({
     *      case (pc, Seq(PUTFIELD(_, _, _), ALOAD_0)) => (pc)
     * }) should be(Seq(...))
     * }}}
     *
     * @note If possible, use one of the more specialized methods, such as, [[collectPair]].
     *       The pure iteration overhead caused by this method is roughly 10-20 times higher
     *       than this one.
     *
     * @param windowSize The size of the sequence of instructions that is passed to the
     *                   partial function.
     *                   It must be larger than 0. **Do not use this method with windowSize "1"**;
     *                   it is more efficient to use the `collect` or `collectWithIndex` methods
     *                   instead.
     *
     * @return The list of results of applying the function f for each matching sequence.
     */
    def slidingCollect[B <: AnyRef](
        windowSize: Int
    )(
        f: PartialFunction[PCAndAnyRef[Queue[Instruction]], B]
    ): List[B] = {
        require(windowSize > 0)

        val max_pc = instructions.length
        var instrs: Queue[Instruction] = Queue.empty
        var firstPC, lastPC = 0
        var elementsInQueue = 0

        //
        // INITIALIZATION
        //
        while (elementsInQueue < windowSize - 1 && lastPC < max_pc) {
            instrs = instrs.enqueue(instructions(lastPC))
            lastPC = pcOfNextInstruction(lastPC)
            elementsInQueue += 1
        }

        //
        // SLIDING OVER THE CODE
        //
        var result: List[B] = List.empty
        while (lastPC < max_pc) {
            instrs = instrs.enqueue(instructions(lastPC))

            val r: Any = f.applyOrElse(PCAndAnyRef(firstPC, instrs), AnyToAnyThis)
            if (r.asInstanceOf[AnyRef] ne AnyToAnyThis) {
                result ::= r.asInstanceOf[B]
            }

            firstPC = pcOfNextInstruction(firstPC)
            lastPC = pcOfNextInstruction(lastPC)
            instrs = instrs.tail
        }

        result.reverse
    }

}

/**
 * Defines constants useful when analyzing a method's code.
 *
 * @author Michael Eichberg
 */
object Code {

    def apply(
        maxStack:          Int,
        maxLocals:         Int,
        instructions:      Array[Instruction],
        exceptionHandlers: ExceptionHandlers  = NoExceptionHandlers,
        attributes:        Attributes         = NoAttributes
    ): Code = {

        var localVariableTablesCount = 0
        var lineNumberTablesCount = 0
        attributes foreach { a =>
            if (a.isInstanceOf[LocalVariableTable]) {
                localVariableTablesCount += 1
            } else if (a.isInstanceOf[UnpackedLineNumberTable]) {
                lineNumberTablesCount += 1
            }
        }

        if (localVariableTablesCount <= 1 && lineNumberTablesCount <= 1) {
            new Code(maxStack, maxLocals, instructions, exceptionHandlers, attributes)
        } else {
            val (localVariableTables, otherAttributes1) =
                partitionByType(attributes, classOf[LocalVariableTable])
            val newAttributes1 =
                if (localVariableTables.nonEmpty && localVariableTables.tail.nonEmpty) {
                    val theLVT = localVariableTables.flatMap[LocalVariable](_.localVariables)
                    otherAttributes1 :+ new LocalVariableTable(theLVT)
                } else {
                    attributes
                }

            val (lineNumberTables, otherAttributes2) =
                partitionByType(newAttributes1, classOf[UnpackedLineNumberTable])
            val newAttributes2 =
                if (lineNumberTables.nonEmpty && lineNumberTables.size > 1) {
                    val mergedTables = lineNumberTables.flatMap[LineNumber](_.lineNumbers)
                    val sortedTable = mergedTables.sortWith((ltA, ltB) => ltA.startPC < ltB.startPC)
                    otherAttributes2 :+ UnpackedLineNumberTable(sortedTable)
                } else {
                    newAttributes1
                }

            new Code(maxStack, maxLocals, instructions, exceptionHandlers, newAttributes2)
        }
    }

    def unapply(
        code: Code
    ): Option[(Int, Int, Array[Instruction], ExceptionHandlers, Attributes)] = {
        import code._
        Some((maxStack, maxLocals, instructions, exceptionHandlers, attributes))
    }

    /**
     * The unique id associated with attributes of kind: [[Code]].
     *
     * `KindId`s can be used for efficient branching on attributes.
     */
    final val KindId = 6

    /**
     * The maximum number of registers required to execute the code - independent
     * of the number of parameters.
     *
     * @note    The method's descriptor may actually require
     */
    def computeMaxLocalsRequiredByCode(instructions: Array[Instruction]): Int = {
        val instructionsLength = instructions.length
        var pc = 0
        var maxRegisterIndex = -1
        var modifiedByWide = false
        do {
            val i: Instruction = instructions(pc)
            if (i == WIDE) {
                modifiedByWide = true
                pc += 1
            } else {
                if (i.writesLocal) {
                    var lastRegisterIndex = i.indexOfWrittenLocal
                    if (i.isStoreLocalVariableInstruction &&
                        i.asStoreLocalVariableInstruction.computationalType.operandSize == 2) {
                        // i.e., not IINC...
                        lastRegisterIndex += 1
                    }
                    if (lastRegisterIndex > maxRegisterIndex) {
                        maxRegisterIndex = lastRegisterIndex
                    }
                }
                pc = i.indexOfNextInstruction(pc, modifiedByWide)
                modifiedByWide = false
            }
        } while (pc < instructionsLength)

        maxRegisterIndex + 1 /* the first register has index 0 */
    }

    def computeMaxLocals(
        isInstanceMethod: Boolean,
        descriptor:       MethodDescriptor,
        instructions:     Array[Instruction]
    ): Int = {
        Math.max(
            computeMaxLocalsRequiredByCode(instructions),
            descriptor.parameterTypes.foldLeft(if (isInstanceMethod) 1 else 0) { (c, n) =>
                c + n.computationalType.operandSize
            }
        )
    }

    def computeCFG(
        instructions:      Array[Instruction],
        exceptionHandlers: ExceptionHandlers  = NoExceptionHandlers,
        classHierarchy:    ClassHierarchy     = ClassHierarchy.PreInitializedClassHierarchy
    ): CFG[Instruction, Code] = {
        CFGFactory(
            Code(Int.MaxValue, Int.MaxValue, instructions, exceptionHandlers),
            classHierarchy
        )
    }

    /**
     * Computes the maximum stack size required when executing this code block.
     *
     * @note If the cfg is available, call the respective `computeMaxStack` method.
     *
     * @throws java.lang.ClassFormatError If the stack size differs between execution paths.
     */
    @throws[ClassFormatError]("if it is impossible to compute the maximum height of the stack")
    def computeMaxStack(
        instructions:      Array[Instruction],
        classHierarchy:    ClassHierarchy     = ClassHierarchy.PreInitializedClassHierarchy,
        exceptionHandlers: ExceptionHandlers  = NoExceptionHandlers
    ): Int = {
        computeMaxStack(
            instructions,
            exceptionHandlers,
            computeCFG(instructions, exceptionHandlers, classHierarchy)
        )
    }

    /**
     * Computes the maximum stack size required when executing this code block.
     *
     * @throws java.lang.ClassFormatError If the stack size differs between execution paths.
     */
    @throws[ClassFormatError]("if it is impossible to compute the maximum height of the stack")
    def computeMaxStack(
        instructions:      Array[Instruction],
        exceptionHandlers: ExceptionHandlers,
        cfg:               CFG[Instruction, Code]
    ): Int = {
        // Basic idea: follow all paths
        var maxStackDepth: Int = 0

        // IntIntPair:  /*PC*/ Int, Int /*stackdepth before executing the instruction*/
        var paths: List[IntIntPair] = List()
        val visitedPCs = new mutable.BitSet(instructions.length)

        // We start with the first instruction and an empty stack.
        paths ::= IntIntPair(0, 0)
        visitedPCs += 0

        // We have to make sure, that all exception handlers are evaluated for
        // max_stack, if an exception is caught, the stack size is always 1 -
        // containing the exception itself.
        for (exceptionHandler <- exceptionHandlers) {
            val handlerPC = exceptionHandler.handlerPC
            if (visitedPCs.add(handlerPC)) paths ::= IntIntPair(handlerPC, 1)
        }

        while (paths.nonEmpty) {
            val stackInfo = paths.head
            val pc = stackInfo._1
            val initialStackDepth = stackInfo._2
            paths = paths.tail
            val stackDepth = initialStackDepth + instructions(pc).stackSlotsChange
            maxStackDepth = Math.max(maxStackDepth, stackDepth)
            cfg.foreachSuccessor(pc) { succPC =>
                if (visitedPCs.add(succPC)) {
                    paths ::= IntIntPair(succPC, stackDepth)
                }
            }
        }

        maxStackDepth
    }

    /**
     * Creates a method body which throws a `java.lang.Error` with the given message or
     * that states that the underlying bytecode is invalid if the message is empty.
     */
    def invalidBytecode(
        descriptor:       MethodDescriptor,
        isInstanceMethod: Boolean,
        message:          Option[String]   = None
    ): Code = {
        new Code(
            maxStack = 3 /* 3 for the message! */ ,
            maxLocals = descriptor.requiredRegisters + (if (isInstanceMethod) 1 else 0),
            instructions =
                Array(
                    NEW(ObjectType.Error), null, null,
                    DUP,
                    message.
                        map(LoadString.apply).
                        getOrElse(LoadString("OPAL: the underlying bytecode is invalid")), null,
                    INVOKESPECIAL(
                        ObjectType.Error,
                        isInterface = false,
                        "",
                        MethodDescriptor.JustTakes(ObjectType.String)
                    ), null, null,
                    ATHROW
                ),
            exceptionHandlers = NoExceptionHandlers,
            attributes = NoAttributes
        )
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy