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

org.opalj.br.cfg.CFGFactory.scala Maven / Gradle / Ivy

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

import scala.annotation.switch

import scala.collection.immutable.IntMap
import scala.collection.immutable.HashMap

import org.opalj.collection.immutable.IntArraySet
import org.opalj.br.instructions.Instruction
import org.opalj.br.instructions.JSRInstruction
import org.opalj.br.instructions.UnconditionalBranchInstruction
import org.opalj.br.instructions.CompoundConditionalBranchInstruction
import org.opalj.br.instructions.TABLESWITCH
import org.opalj.br.instructions.LOOKUPSWITCH
import org.opalj.br.instructions.ATHROW
import org.opalj.br.instructions.JSR
import org.opalj.br.instructions.JSR_W
import org.opalj.br.instructions.RET
import org.opalj.br.instructions.GOTO
import org.opalj.br.instructions.GOTO_W
import org.opalj.br.instructions.INVOKESTATIC
import org.opalj.br.instructions.INVOKEINTERFACE
import org.opalj.br.instructions.INVOKEVIRTUAL
import org.opalj.br.instructions.INVOKESPECIAL
import org.opalj.br.instructions.INVOKEDYNAMIC

/**
 * A factory for computing control flow graphs for methods.
 *
 * @author Michael Eichberg
 */
object CFGFactory {

    def apply(method: Method, classHierarchy: ClassHierarchy): Option[CFG[Instruction, Code]] = {
        method.body.map(code => apply(code, classHierarchy))
    }

    /**
     * Constructs the control flow graph for a given method.
     *
     * The constructed [[CFG]] basically consists of the code's basic blocks. Additionally,
     * two artifical exit nodes are added.
     * One artificial exit node is added to facilitate the navigation to all normal
     * return instructions. A second artificial node is added that enables the navigation
     * to all instructions that led to an abnormal return. Exception handlers are
     * directly added to the graph using [[CatchNode]]s. Each exception handler is
     * associated with exactly one [[CatchNode]] and all instructions that may throw
     * a corresponding exception will have the respective [[CatchNode]] as a successor.
     *
     * @note  The algorithm supports all Java bytecode instructions - in particular JSR/RET.
     *
     * @note  The code is parsed linearly and the graph is therefore constructed implicitly.
     *        Hence, it is possible that the graph contains nodes that cannot be reached from
     *        the start node.
     *
     * @param code A method's body (i.e., the code.)
     * @param classHierarchy The class hierarchy that will be used to determine
     *      if a certain exception is potentially handled by an exception handler.
     */
    def apply(
        implicit
        code:           Code,
        classHierarchy: ClassHierarchy = ClassHierarchy.PreInitializedClassHierarchy
    ): CFG[Instruction, Code] = {

        /*
         * The basic idea of the algorithm is to create the cfg using a single sweep over
         * the instructions and while doing so to determine the basic block boundaries. Here,
         * the idea is that the current basic block is extended to also capture the current
         * instruction unless the previous instruction ended the basic block.
         */

        import classHierarchy.isASubtypeOf

        val instructions = code.instructions
        val codeSize = instructions.length

        val normalReturnNode = new ExitNode(normalReturn = true)
        val abnormalReturnNode = new ExitNode(normalReturn = false)

        // 1. basic initialization
        val bbs = new Array[BasicBlock](codeSize)
        // BBs is a sparse array; only those fields are used that are related to an instruction

        var exceptionHandlers = HashMap.empty[ExceptionHandler, CatchNode]
        for ((exceptionHandler, index) <- code.exceptionHandlers.iterator.zipWithIndex) {
            val catchNode = new CatchNode(exceptionHandler, index)
            exceptionHandlers += (exceptionHandler -> catchNode)
            val handlerPC = exceptionHandler.handlerPC
            var handlerBB = bbs(handlerPC)
            if (handlerBB eq null) {
                handlerBB = new BasicBlock(handlerPC)
                handlerBB.setPredecessors(Set(catchNode))
                bbs(handlerPC) = handlerBB
            } else {
                handlerBB.addPredecessor(catchNode)
            }
            catchNode.setSuccessors(Set(handlerBB))
        }

        // 2. iterate over the code to determine basic block boundaries
        var runningBB: BasicBlock = null
        var previousPC: PC = 0
        var subroutineReturnPCs = IntMap.empty[IntArraySet] // PC => IntArraySet
        code.iterate { (pc, instruction) =>
            if (runningBB eq null) {
                runningBB = bbs(pc)
                if (runningBB eq null)
                    runningBB = new BasicBlock(pc)
            }

            def useRunningBB(): BasicBlock = {
                var currentBB = bbs(pc)
                if (currentBB eq null) {
                    currentBB = runningBB
                    bbs(pc) = currentBB
                } else {
                    // We have hit the beginning of a new basic block;
                    // i.e., this instruction starts a new block.

                    // Let's check if we have to close the previous basic block...
                    if (runningBB ne currentBB) {
                        runningBB.endPC = previousPC
                        runningBB.addSuccessor(currentBB)
                        currentBB.addPredecessor(runningBB)
                        runningBB = currentBB
                    }
                }

                currentBB
            }

            /*
             * Returns the pair consisting of the basic block associated with the current
             * instruction and the BasicBlock create/associated with targetBBStartPC.
             */
            def connect(
                theSourceBB:     BasicBlock,
                targetBBStartPC: PC
            ): ( /*newSourceBB*/ BasicBlock, /*targetBB*/ BasicBlock) = {
                // We ensure that the basic block associated with the PC `targetBBStartPC`
                // actually starts with the given PC.
                val targetBB = bbs(targetBBStartPC)
                if (targetBB eq null) {
                    val newTargetBB = new BasicBlock(targetBBStartPC)
                    newTargetBB.setPredecessors(Set(theSourceBB))
                    theSourceBB.addSuccessor(newTargetBB)
                    bbs(targetBBStartPC) = newTargetBB
                    (theSourceBB, newTargetBB)
                } else if (targetBB.startPC < targetBBStartPC) {
                    // we have to split the basic block...
                    val newTargetBB = new BasicBlock(targetBBStartPC)
                    // if a block has to split itself (due to a back link...)
                    val sourceBB = if (theSourceBB eq targetBB) newTargetBB else theSourceBB
                    newTargetBB.endPC = targetBB.endPC
                    bbs(targetBBStartPC) = newTargetBB
                    // update the bbs associated with the following instruction
                    var nextPC = targetBBStartPC + 1
                    while (nextPC < codeSize) {
                        val nextBB = bbs(nextPC)
                        if (nextBB eq null) {
                            nextPC += 1
                        } else if (nextBB eq targetBB) {
                            bbs(nextPC) = newTargetBB
                            nextPC += 1
                        } else {
                            // we have hit another bb => we're done.
                            nextPC = codeSize
                        }
                    }
                    targetBB.endPC = code.pcOfPreviousInstruction(targetBBStartPC)
                    newTargetBB.setSuccessors(targetBB.successors)
                    targetBB.successors.foreach { targetBBsuccessorBB =>
                        targetBBsuccessorBB.updatePredecessor(oldBB = targetBB, newBB = newTargetBB)
                    }
                    newTargetBB.setPredecessors(Set(sourceBB, targetBB))
                    targetBB.setSuccessors(Set(newTargetBB))
                    sourceBB.addSuccessor(newTargetBB)
                    (sourceBB, newTargetBB)
                } else {
                    assert(
                        targetBB.startPC == targetBBStartPC,
                        s"targetBB's startPC ${targetBB.startPC} does not equal $pc"
                    )

                    theSourceBB.addSuccessor(targetBB)
                    targetBB.addPredecessor(theSourceBB)
                    (theSourceBB, targetBB) // <= return
                }
            }

            def linkWithExceptionHandler(currentBB: BasicBlock, eh: ExceptionHandler): Unit = {
                val catchNode = exceptionHandlers(eh)
                currentBB.addSuccessor(catchNode)
                catchNode.addPredecessor(currentBB)
            }

            def handleExceptions(currentBB: BasicBlock, jvmExceptions: List[ObjectType]): Unit = {
                var areHandled = true
                jvmExceptions.foreach { thrownException =>
                    areHandled &= code.handlersFor(pc).exists { eh =>
                        val catchType = eh.catchType
                        if (catchType.isEmpty) {
                            linkWithExceptionHandler(currentBB, eh)
                            true // also aborts the evaluation
                        } else {
                            val isCaught = isASubtypeOf(thrownException, catchType.get)
                            if (isCaught.isYes) {
                                linkWithExceptionHandler(currentBB, eh)
                                true // also aborts the evaluation
                            } else if (isCaught.isUnknown) {
                                linkWithExceptionHandler(currentBB, eh)
                                false
                            } else {
                                false
                            }
                        }
                    }
                }
                if (!areHandled) {
                    // also connect with exit unless all exceptions are handled
                    currentBB.addSuccessor(abnormalReturnNode)
                    abnormalReturnNode.addPredecessor(currentBB)
                }
            }

            (instruction.opcode: @switch) match {

                case RET.opcode =>
                    // We cannot determine the target instructions at the moment;
                    // we first need to be able to connect the ret instruction with
                    // its jsr instructions.
                    val currentBB = useRunningBB()
                    currentBB.endPC = pc
                    runningBB = null // <=> the next instruction gets a new bb
                case JSR.opcode | JSR_W.opcode =>
                    val jsrInstr = instruction.asInstanceOf[JSRInstruction]
                    val subroutinePC = pc + jsrInstr.branchoffset
                    val thisSubroutineReturnPCs =
                        subroutineReturnPCs.getOrElse(subroutinePC, IntArraySet.empty)
                    subroutineReturnPCs += (
                        subroutinePC ->
                        (thisSubroutineReturnPCs + jsrInstr.indexOfNextInstruction(pc))
                    )
                    val currentBB = useRunningBB()
                    currentBB.endPC = pc
                    /*val subroutineBB = */ connect(currentBB, subroutinePC)
                    runningBB = null // <=> the next instruction gets a new bb

                case ATHROW.opcode =>
                    val currentBB = useRunningBB()
                    currentBB.endPC = pc
                    // We typically don't know anything about the current exception;
                    // hence, we connect this bb with every exception handler in place.
                    var isHandled: Boolean = false
                    val catchNodeSuccessors =
                        code.handlersFor(pc).map { eh =>
                            val catchType = eh.catchType
                            isHandled = isHandled ||
                                catchType.isEmpty || catchType.get == ObjectType.Throwable
                            val catchNode = exceptionHandlers(eh)
                            catchNode.addPredecessor(currentBB)
                            catchNode
                        }.toSet[CFGNode]
                    currentBB.setSuccessors(catchNodeSuccessors)
                    if (!isHandled) {
                        currentBB.addSuccessor(abnormalReturnNode)
                        abnormalReturnNode.addPredecessor(currentBB)
                    }
                    runningBB = null

                case GOTO.opcode | GOTO_W.opcode =>
                    // GOTO WILL NEVER THROW AN EXCEPTION
                    instruction match {
                        case GOTO(3) | GOTO_W(4) =>
                            // THE GOTO INSTRUCTION IS EFFECTIVELY USELESS (A NOP) AS IT IS JUST
                            // A JUMP TO THE NEXT INSTRUCTION; HENCE, WE DO NOT HAVE TO END THE
                            // CURRENT BLOCK.
                            useRunningBB()
                        case _ =>
                            val currentBB = useRunningBB()
                            currentBB.endPC = pc
                            val GOTO = instruction.asInstanceOf[UnconditionalBranchInstruction]
                            connect(currentBB, pc + GOTO.branchoffset)
                            runningBB = null
                    }

                case /*IFs:*/ 165 | 166 | 198 | 199 |
                    159 | 160 | 161 | 162 | 163 | 164 |
                    153 | 154 | 155 | 156 | 157 | 158 =>
                    val IF = instruction.asSimpleConditionalBranchInstruction
                    val currentBB = useRunningBB()
                    currentBB.endPC = pc
                    // jump
                    val (selfBB, _ /*targetBB*/ ) = connect(currentBB, pc + IF.branchoffset)
                    val newCurrentBB = if (selfBB ne currentBB) selfBB else currentBB
                    // fall through case
                    runningBB = connect(newCurrentBB, code.pcOfNextInstruction(pc))._1

                case TABLESWITCH.opcode | LOOKUPSWITCH.opcode =>
                    val SWITCH = instruction.asInstanceOf[CompoundConditionalBranchInstruction]
                    val currentBB = useRunningBB()
                    currentBB.endPC = pc
                    val (selfBB, _ /*targetBB*/ ) = connect(currentBB, pc + SWITCH.defaultOffset)
                    val newCurrentBB = if (selfBB ne currentBB) selfBB else currentBB
                    SWITCH.jumpOffsets.foreach { offset => connect(newCurrentBB, pc + offset) }
                    runningBB = null

                case /*xReturn:*/ 176 | 175 | 174 | 172 | 173 | 177 =>
                    val currentBB = useRunningBB()
                    handleExceptions(currentBB, instruction.jvmExceptions)
                    currentBB.endPC = pc
                    currentBB.addSuccessor(normalReturnNode)
                    normalReturnNode.addPredecessor(currentBB)
                    runningBB = null

                case _ /* INSTRUCTIONS THAT EITHER FALL THROUGH OR THROW A (JVM-BASED) EXCEPTION*/ =>
                    assert(instruction.nextInstructions(pc, regularSuccessorsOnly = true).size == 1)

                    val currentBB = useRunningBB()

                    def endBasicBlock(): Unit = {
                        // this instruction may throw an exception; hence it ends this
                        // basic block
                        currentBB.endPC = pc
                        val nextPC = code.pcOfNextInstruction(pc)
                        runningBB = connect(currentBB, nextPC)._2
                    }

                    instruction.opcode match {
                        case INVOKEDYNAMIC.opcode |
                            INVOKEVIRTUAL.opcode | INVOKEINTERFACE.opcode |
                            INVOKESTATIC.opcode | INVOKESPECIAL.opcode =>
                            // just treat every exception handler as a potential target
                            val isHandled = code.handlersFor(pc).exists { eh =>
                                if (eh.catchType.isEmpty) {
                                    linkWithExceptionHandler(currentBB, eh)
                                    true // also aborts the evaluation
                                } else {
                                    linkWithExceptionHandler(currentBB, eh)
                                    false
                                }
                            }
                            if (!isHandled) {
                                // also connect with exit unless we found a finally handler
                                currentBB.addSuccessor(abnormalReturnNode)
                                abnormalReturnNode.addPredecessor(currentBB)
                            }
                            endBasicBlock()

                        case _ =>
                            val jvmExceptions = instruction.jvmExceptions
                            handleExceptions(currentBB, jvmExceptions)
                            if (jvmExceptions.nonEmpty) {
                                endBasicBlock()
                            }
                    }
            }
            previousPC = pc
        }

        // Analyze the control flow graphs of all subroutines to connect the ret
        // instructions with their correct target addresses.
        if (subroutineReturnPCs.nonEmpty) {
            subroutineReturnPCs.foreach(subroutine => bbs(subroutine._1).setIsStartOfSubroutine())
            for ((subroutinePC, returnToAddresses) <- subroutineReturnPCs) {
                val returnBBs = returnToAddresses.transform[CFGNode, Set[CFGNode]](
                    ra => bbs(ra),
                    Set.newBuilder[CFGNode]
                )
                val subroutineBB = bbs(subroutinePC)
                val subroutineBBs: List[BasicBlock] = subroutineBB.subroutineFrontier(code, bbs)
                val retBBs = subroutineBBs.toSet[CFGNode]
                retBBs.foreach(_.setSuccessors(returnBBs))
                returnBBs.foreach(_.addPredecessors(retBBs))
            }
        }

        val effectiveExceptionHandlers = exceptionHandlers.values filter { catchNode =>
            catchNode.predecessors.nonEmpty || {
                catchNode.successors foreach { successor => successor.removePredecessor(catchNode) }
                false
            }
        }

        CFG(
            code,
            normalReturnNode, abnormalReturnNode, effectiveExceptionHandlers.toList,
            bbs
        )
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy