
org.opalj.br.fpcf.properties.Purity.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 scala.annotation.switch
import org.opalj.collection.immutable.EmptyIntTrieSet
import org.opalj.collection.immutable.IntTrieSet
import org.opalj.fpcf.Entity
import org.opalj.fpcf.FallbackReason
import org.opalj.fpcf.PropertyKey
import org.opalj.fpcf.PropertyMetaInformation
import org.opalj.fpcf.PropertyStore
import org.opalj.br.analyses.SomeProject
import org.opalj.br.fpcf.analyses.ConfiguredPurityKey
import org.opalj.br.fpcf.properties.Purity.ContextuallyPureFlags
import org.opalj.br.fpcf.properties.Purity.ContextuallySideEffectFreeFlags
import org.opalj.br.fpcf.properties.Purity.ImpureFlags
import org.opalj.br.fpcf.properties.Purity.IsNonDeterministic
import org.opalj.br.fpcf.properties.Purity.ModifiesParameters
import org.opalj.br.fpcf.properties.Purity.NotCompileTimePure
import org.opalj.br.fpcf.properties.Purity.PerformsDomainSpecificOperations
import org.opalj.br.fpcf.properties.Purity.PureFlags
import org.opalj.br.fpcf.properties.Purity.SideEffectFreeFlags
sealed trait PurityPropertyMetaInformation extends PropertyMetaInformation {
final type Self = Purity
}
/**
* Describes the level of the purity of a [[org.opalj.br.DeclaredMethod]].
*
* In general, a method is pure if its result only depends on its inputs
* and/or immutable global state and the execution of the method does not have any side effects;
* an instance method's inputs include the current object that is the receiver of the call.
*
* '''The description of the purity levels is inspired by the definition found on wikipedia:'''
*
* [...] a function may be considered a pure function if both of the following statements about
* the function hold:
* - The function always evaluates to the same result value given the same argument value(s).
* The function result value cannot depend on any hidden information or state that may change
* while program execution proceeds or between different executions of the program, nor can it
* depend on any external input from I/O devices.
*
* '''Hence, using true constants (e.g., Math.e) is not a problem as well as creating
* intermediate (mutable) data structures.
* More precisely, methods are pure if the values they refer to always (even across program
* runs) have an identical shape and the precise location in the heap is not relevant (e.g.,
* java.lang.Object.hashCode() and ...toString() are not pure).'''
*
* - Evaluation of the result does not cause any semantically observable side effect or output,
* such as mutation of mutable objects or output to I/O devices.
* The result value need not depend on all (or any) of the argument values. However, it must
* depend on nothing other than the argument values. The function may return multiple result
* values and these conditions must apply to all returned values for the function to be
* considered pure. If an argument is "call-by-reference", any parameter mutation will alter
* the value of the argument outside the function, which will render the function impure.
*
* Given the preceding specification, the purity of a method is described by the subclasses of
* this property. In the following, the prefix of the names of the purity levels are used to
* identify the certainty of the computation of the purity; the letters have the following meaning:
* - LB = Lower Bound; the method is at least <PURITY_LEVEL>, but can still be even more pure.
* - C = Conditional; i.e., the current purity level depends on the purity level of other entities
* (These states are primarily used by the analysis to record the analysis progress.)
* - D = The method is <PURITY_LEVEL> if certain '''Domain-specific''' (non-pure) operations
* are ignored.
*
* [[ImpureByLackOfInformation]] methods have no constraints on their behavior. They may have side effect and
* depend on all accessible (global) state. Analyses can always return `(LB)Impure` as a safe
* default value - even if they are not able to prove that a method is indeed impure; however,
* in the latter case using [[ImpureByAnalysis]] is recommended as this enables subsequent analyses
* to refine the property. There are several implementations of [[ImpureByAnalysis]] which give additional
* reasoning why the analysis classified a method as impure.
*
* [[SideEffectFree]] methods may depend on all accessible (and mutable) state, but may not have
* any side effects.
* In single-threaded execution, this means that the object graph of the program may not
* have changed between invocation of the method and its return, except for potentially additional
* objects allocated by the method. For multi-threaded execution, the object graph may not change
* due to the invocation of the method, again except allocation of new objects. Note that the object
* graph may change during execution of the method due to other methods executing on concurrent
* threads. The method must not have any effects (besides consumption of resources like memory and
* processor time) on methods executing concurrently, in particular it may not acquire any locks on
* objects that concurrent methods could also try to acquire.
*
* Analyses may return [[SideEffectFree]] as a safe default value if they are unable to guarantee
* that a method is [[Pure]], even if it is. However, to return `SideEffectFree` the analysis has
* to guarantee that the method does not have any side effects.
*
* [[Pure]] methods must be side effect free as described above and their result may only
* depend on their parameters (including the receiver object) and global constants. In particular,
* the result of a pure method must be structurally identical each time the method is invoked with
* structurally identical parameters.
* I.e., pure methods may depend on the aliasing relation between their
* parameters or between their parameters and global constants. E.g., the following method is
* pure:
* {{{
* def cmp(s: String) : Boolean = {
* // Reference(!) comparison of s with the interned string "Demo":
* s eq "Demo";
* }
* }}}
* In multi-threaded execution, pure methods can not depend on any mutable state of their
* parameters if that state might be mutated by concurrently executing methods.
*
* Analyses may return [[Pure]] only if they are able to guarantee that a method fulfills these
* requirements.
*
* [[CompileTimePure]] methods additionally may only use global state that is compile-time constant
* (i.e., deterministically initialized to the same value on every execution of the program). If
* their return value is of a reference type, they must return the same reference each time they are
* invoked with identical parameters.
*
* `ExternallySideEffectFree` and `ExternallyPure` methods are also similar to
* [[SideEffectFree]] and [[Pure]] methods, respectively, but may modify their receiver object.
* These properties may be used to detect changes that are confined because the receiver object is
* under the control of the caller.
*
* [[ContextuallySideEffectFree]] and [[ContextuallyPure]] methods may modifiy not only their
* receiver object but all of their parameters. Therefore, these properties can be used to detect
* confined changes because all parameters are under the control of the caller.
*
* [[DSideEffectFree]] and [[DPure]] methods may perform actions that are
* generally considered impure (or non-deterministic in the case of `DPure`), but that
* some clients may want to treat as pure. Such actions include, e.g. logging. A `Rater` is used to
* identify such actions and the properties contain a set of reasons assigned by the Rater.
*
* `DExternallySideEffectFree` and `DExternallyPure` methods are similar, but may again
* modify their receiver, while [[DContextuallySideEffectFree]] and [[DContextuallyPure]]
* methods may modify their parameters.
*
* [[ImpureByLackOfInformation]] is (also) used as the fallback value if no purity information could be
* computed for a method (no analysis is scheduled). Conceptually, clients must treat this in the
* same way as [[ImpureByAnalysis]] except that a future refinement may be possible in case of [[ImpureByAnalysis]].
*
* @author Michael Eichberg
* @author Dominik Helm
*/
sealed abstract class Purity
extends IndividualProperty[Purity, VirtualMethodPurity]
with PurityPropertyMetaInformation {
/**
* The globally unique key of the [[Purity]] property.
*/
final def key: PropertyKey[Purity] = Purity.key
val flags: Int
def isCompileTimePure: Boolean = (flags & NotCompileTimePure) == 0
def isDeterministic: Boolean = (flags & IsNonDeterministic) == 0
def modifiesParameters: Boolean = (flags & ModifiesParameters) != 0
def usesDomainSpecificActions: Boolean = (flags & PerformsDomainSpecificOperations) != 0
final def aggregatedProperty = new VirtualMethodPurity(this)
/**
* Combines this purity value with another one to represent the progress by a purity
* analysis in one phase.
* Conditional as well as unconditional values are combined to the purity level that expresses
* a weaker purity, thereby incorporating the effect of counter-examples to a stronger purity.
* Thus, the result of this operation is used to represent a (potentially conditional) upper
* bound on the possible final result of the purity analysis that performs this operation.
* If one of the combined purity values is conditional and the other is not, the result will be
* the same as if the conditional purity value was combined with the conditional value that
* corresponds to the unconditional value.
*/
override def meet(other: Purity): Purity = {
other match {
case _: ClassifiedImpure => other
case _ if other.modifiesParameters => other.meet(this)
case _ =>
Purity(this.flags | other.flags)
}
}
def withoutContextual: Purity =
if (modifiesParameters) Purity(flags & ~ModifiesParameters) else this
val modifiedParams: IntTrieSet = EmptyIntTrieSet
}
object Purity extends PurityPropertyMetaInformation {
/**
* The key associated with every purity property. The name is "Purity"; the fallback is
* "Impure".
*/
final val key = PropertyKey.create[DeclaredMethod, Purity](
"Purity",
(ps: PropertyStore, _: FallbackReason, e: Entity) => {
e match {
case Context(dm) =>
val conf = ps.context(classOf[SomeProject]).has(ConfiguredPurityKey)
if (conf.isDefined && conf.get.wasSet(dm)) conf.get.purity(dm)
else ImpureByLackOfInformation
case x =>
val m = x.getClass.getSimpleName+
" is not an org.opalj.br.fpcf.properties.Context"
throw new IllegalArgumentException(m)
}
}
)
final val NotCompileTimePure = 0x1
final val IsNonDeterministic = 0x2
final val PerformsDomainSpecificOperations = 0x4
final val ModifiesParameters = 0x8
final val PureFlags = NotCompileTimePure
final val SideEffectFreeFlags = IsNonDeterministic | PureFlags
final val ContextuallyPureFlags = PureFlags | ModifiesParameters
final val ContextuallySideEffectFreeFlags = SideEffectFreeFlags | ModifiesParameters
// There is no flag for impurity as analyses have to treat [[ClassifiedImpure]] specially anyway
final val ImpureFlags = ContextuallySideEffectFreeFlags | PerformsDomainSpecificOperations
/**
* Returns the purity level matching the given flags for internal use by the combine operation
* and unconditional/withoutExternal.
* This will not return Impure/LBImpure as they have to be handled seperately.
*/
private def apply(flags: Int): Purity = (flags: @switch) match {
case CompileTimePure.flags => CompileTimePure
case Pure.flags => Pure
// For non-pure levels, we don't have compile-time purity anymore
case _ => (flags | NotCompileTimePure: @switch) match {
case SideEffectFree.flags => SideEffectFree
case DPure.flags => DPure
case DSideEffectFree.flags => DSideEffectFree
}
}
def apply(name: String): Option[Purity] = name match {
case "CompileTimePure" => Some(CompileTimePure)
case "Pure" => Some(Pure)
case "SideEffectFree" => Some(SideEffectFree)
case "DPure" => Some(DPure)
case "DSideEffectFree" => Some(DSideEffectFree)
case _ if name.startsWith("ContextuallyPure{") =>
Some(ContextuallyPure(parseParams(name.substring(17, name.length - 1))))
case _ if name.startsWith("ContextuallySideEffectFree{") =>
Some(ContextuallySideEffectFree(parseParams(name.substring(27, name.length - 1))))
case _ if name.startsWith("DContextuallyPure{") =>
Some(DContextuallyPure(parseParams(name.substring(18, name.length - 1))))
case _ if name.startsWith("DContextuallySideEffectFree{") =>
Some(DContextuallySideEffectFree(parseParams(name.substring(28, name.length - 1))))
case _ => None
}
def parseParams(s: String): IntTrieSet = {
val params = s.split(',')
var result: IntTrieSet = EmptyIntTrieSet
for (p <- params)
result = result + Integer.valueOf(p)
result
}
}
/**
* The respective method is pure and does depend only on global state that is initialized
* deterministically to the same value on every execution of the program.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object CompileTimePure extends Purity {
final val flags = 0 // <=> no flag is set
final val isRefinable = false
override def meet(other: Purity): Purity = other
}
/**
* The respective method is at least pure.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object Pure extends Purity {
final val flags = PureFlags
}
/**
* The respective method is side-effect free, i.e. it does not have side-effects but its results may
* still be non-deterministic.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object SideEffectFree extends Purity {
final val flags = SideEffectFreeFlags
}
/*
/**
* The respective method may modify its receiver, but is pure otherwise.
*
* A method calling a `ExternallyPure` method can be `Pure` if the receiver of the call is confined
* inside that method.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object ExternallyPure extends Purity {
final val flags = ExternallyPureFlags
}
/**
* The respective method may modify its receiver, but otherwise it is side-effect free, i.e. it does
* not have side effects but its results may still be non-deterministic.
*
* A method calling a `ExternallySideEffectFree` method can be `SideEffectFree` if the receiver of
* the call is confined inside that method.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object ExternallySideEffectFree extends Purity {
final val flags = ExternallySideEffectFreeFlags
}*/
/**
* The respective method may modify its parameters, but is pure otherwise.
*
* A method calling a `ContextuallyPure` method can be `Pure` if the parameters of the call are
* confined inside that method.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case class ContextuallyPure(override val modifiedParams: IntTrieSet) extends Purity {
final val flags = ContextuallyPureFlags
override def meet(other: Purity): Purity = other match {
case _: ClassifiedImpure => other
case ContextuallyPure(p) => ContextuallyPure(this.modifiedParams ++ p)
case ContextuallySideEffectFree(p) => ContextuallySideEffectFree(this.modifiedParams ++ p)
case DContextuallyPure(p) => DContextuallyPure(this.modifiedParams ++ p)
case DContextuallySideEffectFree(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case _ =>
(other.flags: @switch) match {
case CompileTimePure.flags | Pure.flags => this
// For non-pure levels, we don't have compile-time purity anymore
case _ => (other.flags | NotCompileTimePure: @switch) match {
case SideEffectFree.flags => ContextuallySideEffectFree(modifiedParams)
case DPure.flags => DContextuallyPure(modifiedParams)
case DSideEffectFree.flags => DContextuallySideEffectFree(modifiedParams)
}
}
}
}
/**
* The respective method may modify its parameters, but otherwise it is side-effect free, i.e. it
* does not have side effects but its results may still be non-deterministic.
*
* A method calling a `ConteuxtuallySideEffectFree` method can be `SideEffectFree` if the parameters
* of the call are confined inside that method.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case class ContextuallySideEffectFree(override val modifiedParams: IntTrieSet) extends Purity {
final val flags = ContextuallySideEffectFreeFlags
override def meet(other: Purity): Purity = other match {
case _: ClassifiedImpure => other
case ContextuallyPure(p) => ContextuallySideEffectFree(this.modifiedParams ++ p)
case ContextuallySideEffectFree(p) => ContextuallySideEffectFree(this.modifiedParams ++ p)
case DContextuallyPure(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case DContextuallySideEffectFree(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case _ =>
(other.flags: @switch) match {
case CompileTimePure.flags | Pure.flags => this
// For non-pure levels, we don't have compile-time purity anymore
case _ => (other.flags | NotCompileTimePure: @switch) match {
case SideEffectFree.flags => this
case DPure.flags | DSideEffectFree.flags =>
DContextuallySideEffectFree(modifiedParams)
}
}
}
}
/**
* The respective method may perform actions that are generally considered impure or
* non-deterministic that some clients may wish to treat as pure. Otherwise it is pure.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object DPure extends Purity {
final val flags = PureFlags | PerformsDomainSpecificOperations
}
/**
* The respective method may perform actions that are generally considered impure that some clients
* may wish to treat as pure. Otherwise it is side-effect free.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object DSideEffectFree extends Purity {
final val flags = SideEffectFreeFlags | PerformsDomainSpecificOperations
}
/*
/**
* The respective method may perform actions that are generally considered impure or
* non-deterministic that some clients may wish to treat as pure and it may modify its receiver.
* Otherwise it is pure.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object DExternallyPure extends Purity {
final val flags = ExternallyPureFlags | PerformsDomainSpecificOperations
}
/**
* The respective method may perform actions that are generally considered impure that some clients
* may wish to treat as pure and it may modify its receiver. Otherwise it is side-effect free.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case object DExternallySideEffectFree extends Purity {
final val flags = ExternallySideEffectFreeFlags | PerformsDomainSpecificOperations
}*/
/**
* The respective method may perform actions that are generally considered impure or
* non-deterministic that some clients may wish to treat as pure and it may modify its parameters.
* Otherwise it is pure.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case class DContextuallyPure(override val modifiedParams: IntTrieSet) extends Purity {
final val flags = ContextuallyPureFlags | PerformsDomainSpecificOperations
override def meet(other: Purity): Purity = other match {
case _: ClassifiedImpure => other
case ContextuallyPure(p) => DContextuallyPure(this.modifiedParams ++ p)
case ContextuallySideEffectFree(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case DContextuallyPure(p) => DContextuallyPure(this.modifiedParams ++ p)
case DContextuallySideEffectFree(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case _ =>
(other.flags: @switch) match {
case CompileTimePure.flags | Pure.flags => this
// For non-pure levels, we don't have compile-time purity anymore
case _ => (other.flags | NotCompileTimePure: @switch) match {
case SideEffectFree.flags | DSideEffectFree.flags =>
DContextuallySideEffectFree(modifiedParams)
case DPure.flags => this
}
}
}
}
/**
* The respective method may perform actions that are generally considered impure that some clients
* may wish to treat as pure and it may modify its parameters. Otherwise it is side-effect free.
*
* @see [[Purity]] for further details regarding the purity levels.
*/
case class DContextuallySideEffectFree(override val modifiedParams: IntTrieSet) extends Purity {
final val flags = ContextuallySideEffectFreeFlags | PerformsDomainSpecificOperations
override def meet(other: Purity): Purity = other match {
case _: ClassifiedImpure => other
case ContextuallyPure(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case ContextuallySideEffectFree(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case DContextuallyPure(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case DContextuallySideEffectFree(p) => DContextuallySideEffectFree(this.modifiedParams ++ p)
case _ => this
}
}
/**
* Clients have to treat the method as impure. If the property is refinable clients can keep
* the dependency.
*/
sealed abstract class ClassifiedImpure extends Purity {
final val flags = ImpureFlags
override val withoutContextual: ClassifiedImpure = this
}
/**
* The method needs to be treated as impure for the time being. However, the current
* analysis is not able to derive a more precise result; no more dependency exist.
*/
case object ImpureByAnalysis extends ClassifiedImpure {
override def meet(other: Purity): Purity = {
other match {
case ImpureByLackOfInformation => ImpureByLackOfInformation
case _ => this
}
}
}
/** The method is (finally classified as) impure; this also models the fallback. */
case object ImpureByLackOfInformation extends ClassifiedImpure {
override def meet(other: Purity): Purity = this
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy