
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