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

org.opalj.br.fpcf.properties.ThrownExceptionsFallback.scala Maven / Gradle / Ivy

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

import org.opalj.fpcf.Entity
import org.opalj.fpcf.FallbackReason
import org.opalj.fpcf.PropertyComputation
import org.opalj.fpcf.PropertyComputationResult
import org.opalj.fpcf.PropertyStore
import org.opalj.fpcf.Result
import org.opalj.br.analyses.Project
import org.opalj.br.collection.mutable.{TypesSet => BRMutableTypesSet}
import org.opalj.br.fpcf.properties.ThrownExceptions.MethodBodyIsNotAvailable
import org.opalj.br.fpcf.properties.ThrownExceptions.MethodIsNative
import org.opalj.br.fpcf.properties.ThrownExceptions.NoExceptions
import org.opalj.br.instructions.ALOAD_0
import org.opalj.br.instructions.ARETURN
import org.opalj.br.instructions.ASTORE
import org.opalj.br.instructions.ASTORE_0
import org.opalj.br.instructions.ATHROW
import org.opalj.br.instructions.DRETURN
import org.opalj.br.instructions.DSTORE
import org.opalj.br.instructions.DSTORE_0
import org.opalj.br.instructions.FRETURN
import org.opalj.br.instructions.FSTORE
import org.opalj.br.instructions.FSTORE_0
import org.opalj.br.instructions.GETFIELD
import org.opalj.br.instructions.IDIV
import org.opalj.br.instructions.Instruction
import org.opalj.br.instructions.INVOKEDYNAMIC
import org.opalj.br.instructions.INVOKEINTERFACE
import org.opalj.br.instructions.INVOKESPECIAL
import org.opalj.br.instructions.INVOKESTATIC
import org.opalj.br.instructions.INVOKEVIRTUAL
import org.opalj.br.instructions.IREM
import org.opalj.br.instructions.IRETURN
import org.opalj.br.instructions.ISTORE
import org.opalj.br.instructions.ISTORE_0
import org.opalj.br.instructions.LDCInt
import org.opalj.br.instructions.LDIV
import org.opalj.br.instructions.LoadLong
import org.opalj.br.instructions.LREM
import org.opalj.br.instructions.LRETURN
import org.opalj.br.instructions.LSTORE
import org.opalj.br.instructions.LSTORE_0
import org.opalj.br.instructions.MONITORENTER
import org.opalj.br.instructions.MONITOREXIT
import org.opalj.br.instructions.PUTFIELD
import org.opalj.br.instructions.RETURN
import org.opalj.br.instructions.StackManagementInstruction

/**
 * A very straight forward flow-insensitive analysis which can successfully analyze methods
 * with respect to the potentially thrown exceptions under the conditions that no other
 * methods are invoked and that no exceptions are explicitly thrown (`ATHROW`). This analysis
 * always computes a sound over approximation of the potentially thrown exceptions.
 *
 * The analysis has limited support for the following cases to be more precise in case of
 * common code patterns (e.g., a standard getter):
 *  - If all instance based field reads are using the self reference "this" and
 *    "this" is used in the expected manner
 *  - If no [[org.opalj.br.instructions.MONITORENTER]]/[[org.opalj.br.instructions.MONITOREXIT]]
 *    instructions are found, the return instructions will not throw
 *    `IllegalMonitorStateException`s.
 *
 * Hence, the primary use case of this method is to identify those methods that are guaranteed
 * to '''never throw exceptions'''.
 */
object ThrownExceptionsFallback extends ((PropertyStore, FallbackReason, Entity) => ThrownExceptions) {

    final val ObjectEqualsMethodDescriptor = MethodDescriptor(ObjectType.Object, BooleanType)

    def apply(ps: PropertyStore, reason: FallbackReason, e: Entity): ThrownExceptions = {
        e match { case m: Method => this(ps, m) }
    }

    def apply(ps: PropertyStore, e: Entity): ThrownExceptions = {
        e match { case m: Method => this(ps, m) }
    }

    def apply(ps: PropertyStore, m: Method): ThrownExceptions = {
        if (m.isNative)
            return MethodIsNative;
        if (m.isAbstract)
            return NoExceptions; // Method is abstract...
        val body = m.body
        if (body.isEmpty)
            return MethodBodyIsNotAvailable;

        //
        //... when we reach this point the method is non-empty
        //
        val code = body.get
        val cfJoins = code.cfJoins
        val instructions = code.instructions
        val isStaticMethod = m.isStatic

        val exceptions = new BRMutableTypesSet(ps.context(classOf[Project[_]]).classHierarchy)

        var result: ThrownExceptions = null

        var isSynchronizationUsed = false

        var isLocalVariable0Updated = false
        var fieldAccessMayThrowNullPointerException = false
        var isFieldAccessed = false

        /* Implicitly (i.e., as a side effect) collects the thrown exceptions in the exceptions set.
         *
         * @return `true` if it is possible to collect all potentially thrown exceptions.
         */
        def collectAllExceptions(pc: PC, instruction: Instruction): Boolean = {
            instruction.opcode match {

                case ATHROW.opcode =>
                    result = ThrownExceptions.UnknownExceptionIsThrown
                    false
                case INVOKESPECIAL.opcode =>
                    val INVOKESPECIAL(declaringClass, _, name, descriptor) = instruction
                    if ((declaringClass eq ObjectType.Object) && (
                        (name == "" && descriptor == MethodDescriptor.NoArgsAndReturnVoid) ||
                        (name == "hashCode" && descriptor == MethodDescriptor.JustReturnsInteger) ||
                        (name == "equals" && descriptor == ObjectEqualsMethodDescriptor) ||
                        (name == "toString" && descriptor == MethodDescriptor.JustReturnsString)
                    )) {
                        true
                    } else {
                        result = ThrownExceptions.AnalysisLimitation
                        false
                    }

                case INVOKEDYNAMIC.opcode =>
                    // if the bytecode is valid, the creation of the call site object should
                    // succeed...
                    true

                case INVOKESTATIC.opcode | INVOKEINTERFACE.opcode | INVOKEVIRTUAL.opcode =>
                    result = ThrownExceptions.AnalysisLimitation
                    false

                // let's determine if the register 0 is updated (i.e., if the register which
                // stores the this reference in case of instance methods is updated)
                case ISTORE_0.opcode | LSTORE_0.opcode |
                    DSTORE_0.opcode | FSTORE_0.opcode |
                    ASTORE_0.opcode =>
                    isLocalVariable0Updated = true
                    true
                case ISTORE.opcode | LSTORE.opcode |
                    FSTORE.opcode | DSTORE.opcode |
                    ASTORE.opcode =>
                    val lvIndex = instruction.indexOfWrittenLocal
                    if (lvIndex == 0) isLocalVariable0Updated = true
                    true

                case GETFIELD.opcode =>
                    isFieldAccessed = true
                    fieldAccessMayThrowNullPointerException ||=
                        isStaticMethod || // <= the receiver is some object
                        isLocalVariable0Updated || // <= we don't know the receiver object at all
                        cfJoins.contains(pc) || // <= we cannot locally decide who is the receiver
                        instructions(code.pcOfPreviousInstruction(pc)) != ALOAD_0 // <= the receiver may be null..
                    true

                case PUTFIELD.opcode =>
                    isFieldAccessed = true
                    fieldAccessMayThrowNullPointerException =
                        fieldAccessMayThrowNullPointerException ||
                            isStaticMethod || // <= the receiver is some object
                            isLocalVariable0Updated || // <= we don't know the receiver object at all
                            cfJoins.contains(pc) || // <= we cannot locally decide who is the receiver
                            {
                                val predecessorPC = code.pcOfPreviousInstruction(pc)
                                val predecessorOfPredecessorPC =
                                    code.pcOfPreviousInstruction(predecessorPC)
                                val valueInstruction = instructions(predecessorPC)

                                instructions(predecessorOfPredecessorPC) != ALOAD_0 || // <= the receiver may be null..
                                    valueInstruction.isInstanceOf[StackManagementInstruction] ||
                                    // we have to ensure that our "this" reference is not used for something else... =>
                                    valueInstruction.numberOfPoppedOperands(NotRequired) > 0
                                // the number of pushed operands is always equal or smaller than 1
                                // except of the stack management instructions
                            }
                    true

                case MONITORENTER.opcode | MONITOREXIT.opcode =>
                    exceptions ++= instruction.jvmExceptions
                    isSynchronizationUsed = true
                    true
                case IRETURN.opcode | LRETURN.opcode |
                    FRETURN.opcode | DRETURN.opcode |
                    ARETURN.opcode | RETURN.opcode =>
                    // let's forget about the IllegalMonitorStateException for now unless we have
                    // a MONITORENTER/MONITOREXIT instruction
                    true

                case IREM.opcode | IDIV.opcode =>
                    if (!cfJoins.contains(pc)) {
                        val predecessorPC = code.pcOfPreviousInstruction(pc)
                        val valueInstruction = instructions(predecessorPC)
                        valueInstruction match {
                            case LDCInt(value) if value != 0 =>
                                // there will be no arithmetic exception
                                true
                            case _ =>
                                exceptions ++= instruction.jvmExceptions
                                true
                        }
                    } else {
                        exceptions ++= instruction.jvmExceptions
                        true
                    }

                case LREM.opcode | LDIV.opcode =>
                    if (!cfJoins.contains(pc)) {
                        val predecessorPC = code.pcOfPreviousInstruction(pc)
                        val valueInstruction = instructions(predecessorPC)
                        valueInstruction match {
                            case LoadLong(value) if value != 0L =>
                                // there will be no arithmetic exception
                                true
                            case _ =>
                                exceptions ++= instruction.jvmExceptions
                                true
                        }
                    } else {
                        exceptions ++= instruction.jvmExceptions
                        true
                    }

                case _ /* all other instructions */ =>
                    exceptions ++= instruction.jvmExceptions
                    true
            }
        }
        val areAllExceptionsCollected = code.forall(collectAllExceptions(_, _))
        if (!areAllExceptionsCollected) {
            assert(result ne null)
            return result;
        }
        if (fieldAccessMayThrowNullPointerException ||
            (isFieldAccessed && isLocalVariable0Updated)) {
            exceptions += ObjectType.NullPointerException
        }
        if (isSynchronizationUsed) {
            exceptions += ObjectType.IllegalMonitorStateException
        }

        if (exceptions.isEmpty)
            NoExceptions
        else
            ThrownExceptions(exceptions)
    }

}

class ThrownExceptionsFallback(ps: PropertyStore) extends PropertyComputation[Method] {

    def apply(m: Method): PropertyComputationResult = {
        Result(m, ThrownExceptionsFallback(ps, m))
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy