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

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