diff --git a/.gitignore b/.gitignore index acc4189..23e0ca1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,11 @@ lib/target/ -project/target/ target/ reactive-async.iws reactive-async.ipr .idea *~ + +.metals/ +.bloop/ +.bsp/ +project/metals.sbt diff --git a/.scalafmt.conf b/.scalafmt.conf new file mode 100644 index 0000000..b489f49 --- /dev/null +++ b/.scalafmt.conf @@ -0,0 +1,5 @@ +version = 3.9.4 +runner.dialect = scala213 + +maxColumn = 100 +spaces.inImportCurlyBraces = true \ No newline at end of file diff --git a/bench/src/test/scala/com/phaller/rasync/bench/fpbenchmarks.scala b/bench/src/test/scala/com/phaller/rasync/bench/fpbenchmarks.scala index e3252b5..554ee7a 100644 --- a/bench/src/test/scala/com/phaller/rasync/bench/fpbenchmarks.scala +++ b/bench/src/test/scala/com/phaller/rasync/bench/fpbenchmarks.scala @@ -8,10 +8,8 @@ import org.scalameter.picklers.noPickler._ object FuturesAndPromisesBenchmarks extends PerformanceTest.Microbenchmark { /* configuration */ - override def executor = LocalExecutor( - new Executor.Warmer.Default, - Aggregator.min, - new Measurer.Default) + override def executor = + LocalExecutor(new Executor.Warmer.Default, Aggregator.min, new Measurer.Default) override def reporter = new LoggingReporter override def persistor = Persistor.None @@ -21,42 +19,37 @@ object FuturesAndPromisesBenchmarks extends PerformanceTest.Microbenchmark { /* creation of promises */ performance of "Promises" in { measure method "creating" in { - using(size) config ( - exec.benchRuns -> 9) in { - r => for (i <- 1 to r) Promise[Int]() - } + using(size) config (exec.benchRuns := 9) in { r => + for (i <- 1 to r) Promise[Int]() + } } } /* creation and completion of futures */ performance of "Promises" in { measure method "creating and completing" in { - using(size) config ( - exec.benchRuns -> 9) in { - r => - for (i <- 1 to r) { - val p = Promise[Int] - p.success(1) - } + using(size) config (exec.benchRuns := 9) in { r => + for (i <- 1 to r) { + val p = Promise[Int] + p.success(1) } + } } } /* refinement of promises */ performance of "Promises" in { measure method "refinement" in { - using(Gen.unit(s"$nrOfPromises promises")) config ( - exec.benchRuns -> 9) in { - (Unit) => - { - var i = 0 - val promises = createListPromises(nrOfPromises, List.empty) - for (p <- promises) { - i = i + 1 - p.success(i) - } - } + using(Gen.unit(s"$nrOfPromises promises")) config (exec.benchRuns := 9) in { (Unit) => + { + var i = 0 + val promises = createListPromises(nrOfPromises, List.empty) + for (p <- promises) { + i = i + 1 + p.success(i) + } } + } } } diff --git a/bench/src/test/scala/com/phaller/rasync/bench/rabenchmarks.scala b/bench/src/test/scala/com/phaller/rasync/bench/rabenchmarks.scala index 6d3d50a..1e61c52 100644 --- a/bench/src/test/scala/com/phaller/rasync/bench/rabenchmarks.scala +++ b/bench/src/test/scala/com/phaller/rasync/bench/rabenchmarks.scala @@ -13,10 +13,8 @@ import scala.concurrent.duration._ object ReactiveAsyncBenchmarks extends PerformanceTest.Microbenchmark { /* configuration */ - override def executor = LocalExecutor( - new Executor.Warmer.Default, - Aggregator.min, - new Measurer.Default) + override def executor = + LocalExecutor(new Executor.Warmer.Default, Aggregator.min, new Measurer.Default) override def reporter = new LoggingReporter override def persistor = Persistor.None @@ -30,49 +28,43 @@ object ReactiveAsyncBenchmarks extends PerformanceTest.Microbenchmark { /* creation of cells/cell completers */ performance of "Cells" in { measure method "creating" in { - using(size) config ( - exec.benchRuns -> 9) in { - r => - { - implicit val pool = new HandlerPool(NaturalNumberKey, nrOfThreads) - for (i <- 1 to r) - pool.execute(() => { CellCompleter[Int, Null]() }: Unit) - waitUntilQuiescent(pool) - } + using(size) config (exec.benchRuns := 9) in { r => + { + implicit val pool = new HandlerPool(NaturalNumberKey, nrOfThreads) + for (i <- 1 to r) + pool.execute(() => { CellCompleter[Int, Null]() }: Unit) + waitUntilQuiescent(pool) } + } } } /* completion of cells */ performance of "Cells" in { measure method "create and putFinal" in { - using(size) config ( - exec.benchRuns -> 9) in { - r => - { - implicit val pool = new HandlerPool(NaturalNumberKey, nrOfThreads) - for (i <- 1 to r) { - pool.execute(() => { - val cellCompleter = CellCompleter[Int, Null]() - cellCompleter.putFinal(1) - }) - } - waitUntilQuiescent(pool) - } + using(size) config (exec.benchRuns := 9) in { r => + { + implicit val pool = new HandlerPool(NaturalNumberKey, nrOfThreads) + for (i <- 1 to r) { + pool.execute(() => { + val cellCompleter = CellCompleter[Int, Null]() + cellCompleter.putFinal(1) + }) + } + waitUntilQuiescent(pool) } + } } } performance of "Cells" in { measure method "putNext" in { - using(Gen.unit(s"$nrOfCells cells")) config ( - exec.benchRuns -> 9) in { - (Unit) => - implicit val pool = new HandlerPool(NaturalNumberKey, nrOfThreads) - val cellCompleter = CellCompleter[Int, Null]() - for (i <- 1 to nrOfCells) pool.execute(() => cellCompleter.putNext(i)) - waitUntilQuiescent(pool) - } + using(Gen.unit(s"$nrOfCells cells")) config (exec.benchRuns := 9) in { (Unit) => + implicit val pool = new HandlerPool(NaturalNumberKey, nrOfThreads) + val cellCompleter = CellCompleter[Int, Null]() + for (i <- 1 to nrOfCells) pool.execute(() => cellCompleter.putNext(i)) + waitUntilQuiescent(pool) + } } } diff --git a/build.sbt b/build.sbt index a1eadd9..f59c318 100644 --- a/build.sbt +++ b/build.sbt @@ -1,16 +1,16 @@ import Dependencies._ // see project/Dependencies.scala -import Util._ // see project/Util.scala +import Version._ // see project/Version.scala -val buildVersion = "0.2.1-SNAPSHOT" -organization in ThisBuild := "com.phaller" -licenses in ThisBuild += ("BSD 2-Clause", url("http://opensource.org/licenses/BSD-2-Clause")) +val buildVersion = "0.2.2-SNAPSHOT" +ThisBuild / organization := "com.phaller" +ThisBuild / licenses += ("BSD 2-Clause", url("http://opensource.org/licenses/BSD-2-Clause")) def commonSettings = Seq( - version in ThisBuild := buildVersion, + ThisBuild / version := buildVersion, scalaVersion := buildScalaVersion, logBuffered := false, - parallelExecution in Test := false, - resolvers in ThisBuild += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots" + Test / parallelExecution := false, + ThisBuild / resolvers += "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots" ) def noPublish = Seq( @@ -18,9 +18,9 @@ def noPublish = Seq( publishLocal := {} ) -lazy val core: Project = (project in file("core")). - settings(commonSettings: _*). - settings( +lazy val core: Project = (project in file("core")) + .settings(commonSettings: _*) + .settings( name := "reactive-async", libraryDependencies += scalaTest, libraryDependencies += opalCommon, @@ -28,32 +28,34 @@ lazy val core: Project = (project in file("core")). scalacOptions += "-feature" ) -lazy val npv: Project = (project in file("monte-carlo-npv")). - settings(commonSettings: _*). - settings( +lazy val npv: Project = (project in file("monte-carlo-npv")) + .settings(commonSettings: _*) + .settings( name := "reactive-async-npv", scalacOptions += "-feature", - skip in publish := true - ). - dependsOn(core) + publish / skip := true + ) + .dependsOn(core) lazy val Benchmark = config("bench") extend Test -lazy val bench: Project = (project in file("bench")). - settings(commonSettings: _*). - settings( +lazy val bench: Project = (project in file("bench")) + .settings(commonSettings: _*) + .settings( name := "reactive-async-bench", libraryDependencies += scalaTest, libraryDependencies += opalCommon, // libraryDependencies += opalAI % Test, libraryDependencies += scalaMeter, testFrameworks += new TestFramework("org.scalameter.ScalaMeterFramework"), - skip in publish := true - ).configs( + publish / skip := true + ) + .configs( Benchmark - ).settings( + ) + .settings( inConfig(Benchmark)(Defaults.testSettings): _* - ). - dependsOn(core) + ) + .dependsOn(core) -javaOptions in ThisBuild ++= Seq("-Xmx27G", "-Xms1024m", "-XX:ThreadStackSize=2048") +ThisBuild / javaOptions ++= Seq("-Xmx27G", "-Xms1024m", "-XX:ThreadStackSize=2048") diff --git a/core/src/main/scala/com/phaller/rasync/cell/Cell.scala b/core/src/main/scala/com/phaller/rasync/cell/Cell.scala index 6ef06df..d18f888 100644 --- a/core/src/main/scala/com/phaller/rasync/cell/Cell.scala +++ b/core/src/main/scala/com/phaller/rasync/cell/Cell.scala @@ -20,49 +20,57 @@ trait Cell[V, E >: Null] { // some entity this cell represents val entity: E = null - /** - * Returns the current value of `this` `Cell`. - * - * Note that this method may return non-deterministic values. To ensure - * deterministic executions use the quiescence API of class `HandlerPool`. - */ + /** Returns the current value of `this` `Cell`. + * + * Note that this method may return non-deterministic values. To ensure deterministic executions + * use the quiescence API of class `HandlerPool`. + */ def getResult(): V def getTry(): Try[V] def getState(): Try[ValueOutcome[V]] - /** - * Start computations associated with this cell. - * The init method is called and relevant cells are triggered recursivly. - */ + /** Start computations associated with this cell. The init method is called and relevant cells are + * triggered recursivly. + */ def trigger(): Unit /** Return true, iff this cell has been completed. */ def isComplete: Boolean - /** - * Adds a dependency on some `other` cell. - * - * Example: - * {{{ - * when(cell, { // when the next value or final value is put into `cell` - * case (_, Impure) => FinalOutcome(Impure) // if the next value of `cell` is `Impure`, `this` cell is completed with value `Impure` - * case (true, Pure) => FinalOutcome(Pure)// if the final value of `cell` is `Pure`, `this` cell is completed with `Pure`. - * case _ => NoOutcome - * }) - * }}} - * - * @param valueCallback Callback that receives the new value of `other` and returns an `Outcome` for `this` cell. - * @param other Cells that `this` Cell depends on. - */ - def when(other: Iterable[Cell[V, E]])(valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V]): Unit + + /** Adds a dependency on some `other` cell. + * + * Example: + * {{{ + * when(cell, { // when the next value or final value is put into `cell` + * case (_, Impure) => FinalOutcome(Impure) // if the next value of `cell` is `Impure`, `this` cell is completed with value `Impure` + * case (true, Pure) => FinalOutcome(Pure)// if the final value of `cell` is `Pure`, `this` cell is completed with `Pure`. + * case _ => NoOutcome + * }) + * }}} + * + * @param valueCallback + * Callback that receives the new value of `other` and returns an `Outcome` for `this` cell. + * @param other + * Cells that `this` Cell depends on. + */ + def when(other: Iterable[Cell[V, E]])( + valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] + ): Unit // Convenience methods for one or two other cells - def when(other: Cell[V, E])(valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V]): Unit - def when(other1: Cell[V, E], other2: Cell[V, E])(valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V]): Unit + def when(other: Cell[V, E])( + valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] + ): Unit + def when(other1: Cell[V, E], other2: Cell[V, E])( + valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] + ): Unit // internal API // Schedules execution of `callback` when next intermediate result is available. (not thread safe!) - private[rasync] def onNext[U](callback: Try[V] => U): Unit //(implicit context: ExecutionContext): Unit + private[rasync] def onNext[U]( + callback: Try[V] => U + ): Unit // (implicit context: ExecutionContext): Unit // Schedules execution of `callback` when completed with final result. (not thread safe!) private[rasync] def onComplete[U](callback: Try[V] => U): Unit @@ -71,12 +79,10 @@ trait Cell[V, E >: Null] { private[rasync] def waitUntilNoDeps(): Unit private[rasync] def waitUntilNoDeps(timeout: Long, unit: TimeUnit): Unit - /** - * Returns true, iff computations relevant to this cell are running. - * A cell becomes active, when it is triggered via its `trigger` method, which in turn - * calls the `init` method. - * A cell becomes inactive, when it is completed. - */ + /** Returns true, iff computations relevant to this cell are running. A cell becomes active, when + * it is triggered via its `trigger` method, which in turn calls the `init` method. A cell + * becomes inactive, when it is completed. + */ private[rasync] def tasksActive(): Boolean private[rasync] def setTasksActive(): Boolean @@ -91,14 +97,15 @@ trait Cell[V, E >: Null] { private[rasync] def addUpdate(dependee: Cell[V, E]): Unit - /** - * Put a final value to `this` cell, but do not propagate to some cells. - * - * This is used for cycle resolution to not create cycle propagations. - * - * @param value The value to put. - * @param dontCall The cells that won't be informed about the update. - */ + /** Put a final value to `this` cell, but do not propagate to some cells. + * + * This is used for cycle resolution to not create cycle propagations. + * + * @param value + * The value to put. + * @param dontCall + * The cells that won't be informed about the update. + */ private[rasync] def resolveWithValue(value: Try[V], dontCall: Seq[Cell[V, E]]): Unit private[rasync] def isIndependent(): Boolean @@ -112,10 +119,9 @@ trait ConcurrentCell[V, E >: Null] extends Cell[V, E] { override val sequential = false } -/** - * `CellImpl` uses a `State` to store the current value and dependency information. - * A state can either be an `IntermediateState` or a `FinalState`. - */ +/** `CellImpl` uses a `State` to store the current value and dependency information. A state can + * either be an `IntermediateState` or a `FinalState`. + */ private trait State[V] { val res: Try[V] } @@ -128,7 +134,12 @@ private trait State[V] { * @param deps dependent Cells + a staged value for this cell (this is not needed for completeDeps, the staged value is always the final one) * @param callbacks those callbacks are run if the cell t(hat we depend on) changes. */ -private class IntermediateState[V, E >: Null](override val res: Success[V], val tasksActive: Boolean, val dependees: Map[Cell[V, E], CallbackRunnable[V, E]], val dependers: Set[Cell[V, E]]) extends State[V] +private class IntermediateState[V, E >: Null]( + override val res: Success[V], + val tasksActive: Boolean, + val dependees: Map[Cell[V, E], CallbackRunnable[V, E]], + val dependers: Set[Cell[V, E]] +) extends State[V] private object IntermediateState { def empty[V, E >: Null](updater: Updater[V]): IntermediateState[V, E] = @@ -136,24 +147,29 @@ private object IntermediateState { } private class FinalState[V]( - /** The final result of this cell.*/ - override val res: Try[V] -/* - * When a cell is completed, all `completeDependentCells` are copied from the IntermediateState - * to the FinalState. That way, we know that the dependentCell may poll a staged value (at most) one - * more time. After that, the dependentCell doese not depend on `this` cell any more and is removed - * from `completeDependentCells`. Repeated polls will return in NoOutcome. - */ + /** The final result of this cell. */ + override val res: Try[V] + /* + * When a cell is completed, all `completeDependentCells` are copied from the IntermediateState + * to the FinalState. That way, we know that the dependentCell may poll a staged value (at most) one + * more time. After that, the dependentCell doese not depend on `this` cell any more and is removed + * from `completeDependentCells`. Repeated polls will return in NoOutcome. + */ // val completeDependentCells: TrieMap[Cell[V, E], Unit], // val nextDependentCells: TrieMap[Cell[V, E], Unit] ) extends State[V] -/** - * Implementation of traits Cell and CellCompleter as the same run-time object. - * - * Instances of the class use a `State` to store the current value and dependency information. - */ -private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], updater: Updater[V], override val init: (Cell[V, E]) => Outcome[V], override val entity: E) extends Cell[V, E] with CellCompleter[V, E] { +/** Implementation of traits Cell and CellCompleter as the same run-time object. + * + * Instances of the class use a `State` to store the current value and dependency information. + */ +private[rasync] abstract class CellImpl[V, E >: Null]( + pool: HandlerPool[V, E], + updater: Updater[V], + override val init: (Cell[V, E]) => Outcome[V], + override val entity: E +) extends Cell[V, E] + with CellCompleter[V, E] { implicit object SeqTaskOrdering extends Ordering[(Int, () => _)] { override def compare(x: (Int, () => _), y: (Int, () => _)): Int = x._1 - y._1 @@ -168,9 +184,13 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u * (a) `FinalState[V]` for the final result, or * (b) `IntermediateState[K,V]` for an incomplete state. */ - private val state: AtomicReference[State[V]] = new AtomicReference(IntermediateState.empty[V, E](updater)) + private val state: AtomicReference[State[V]] = new AtomicReference( + IntermediateState.empty[V, E](updater) + ) - private val queuedCallbacks = new AtomicReference(new mutable.PriorityQueue[(Int, () => _)]()(SeqTaskOrdering)) + private val queuedCallbacks = new AtomicReference( + new mutable.PriorityQueue[(Int, () => _)]()(SeqTaskOrdering) + ) // A list of callbacks to call, when `this` cell is completed/updated. // Note that this is not sync'ed with the state in any way, so calling `onComplete` or `onNext` @@ -184,35 +204,43 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u val newQ = currentQ.clone() newQ.enqueue((prio, f)) if (queuedCallbacks.compareAndSet(currentQ, newQ)) { - if (currentQ.isEmpty) startSequentialTask() // we could use the current thread for the first task. Refactor! + if (currentQ.isEmpty) + startSequentialTask() // we could use the current thread for the first task. Refactor! } else sequential(f, prio) } - /** Submit a queued sequential task to the handler pool. Continues submitting until the queue is empty. */ + /** Submit a queued sequential task to the handler pool. Continues submitting until the queue is + * empty. + */ private def startSequentialTask(): Unit = { val cell = this - queuedCallbacks.get().headOption.foreach({ - case (prio, task) => + queuedCallbacks + .get() + .headOption + .foreach({ case (prio, task) => // Take the first element but do not remove it yet. // When other tasks are added they will find this task in the // queue and not start a new thread. - pool.execute(new Runnable { - override def run(): Unit = { - try { - task.apply() - } catch { - // An exception thrown in a callback is stored as the final value for the depender - case e: Exception => cell.putFailure(Failure(e)) + pool.execute( + new Runnable { + override def run(): Unit = { + try { + task.apply() + } catch { + // An exception thrown in a callback is stored as the final value for the depender + case e: Exception => cell.putFailure(Failure(e)) + } + + // We have completed the head task. + // Now remove it from the queue. If there are more elements + // left, start the next one + removeCompletedSequentialTaks() } - - // We have completed the head task. - // Now remove it from the queue. If there are more elements - // left, start the next one - removeCompletedSequentialTaks() - } - }, prio) - }) + }, + prio + ) + }) } /** Call this from a thread that has completed the head of the queued sequential tasks. */ @@ -233,7 +261,7 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u override def getResult(): V = state.get.res match { case Success(result) => result - case Failure(err) => throw new IllegalStateException(err) + case Failure(err) => throw new IllegalStateException(err) } override def getTry(): Try[V] = @@ -252,7 +280,7 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u override def isComplete: Boolean = state.get match { case _: FinalState[V] => true - case _ => false + case _ => false } override def putFinal(x: V): Unit = { @@ -315,16 +343,22 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u tryComplete(value, Some(dontCall)) } - override final def when(other: Cell[V, E])(valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V]): Unit = { + override final def when( + other: Cell[V, E] + )(valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V]): Unit = { when(Iterable(other))(valueCallback) } - override final def when(other1: Cell[V, E], other2: Cell[V, E])(valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V]): Unit = { + override final def when(other1: Cell[V, E], other2: Cell[V, E])( + valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] + ): Unit = { when(Iterable(other1, other2))(valueCallback) } @tailrec - override final def when(other: Iterable[Cell[V, E]])(valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V]): Unit = state.get match { + override final def when(other: Iterable[Cell[V, E]])( + valueCallback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] + ): Unit = state.get match { case _: FinalState[V] => // completed with final result // do not add dependency // in fact, do nothing @@ -334,7 +368,12 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u if (sequential) new SequentialCallbackRunnable[V, E](pool, this, valueCallback) else new ConcurrentCallbackRunnable[V, E](pool, this, valueCallback) - val newState = new IntermediateState[V, E](current.res, current.tasksActive, current.dependees ++ other.iterator.map(_ → newCallback), current.dependers) + val newState = new IntermediateState[V, E]( + current.res, + current.tasksActive, + current.dependees ++ other.iterator.map(_ -> newCallback), + current.dependers + ) if (state.compareAndSet(current, newState)) { other.foreach(c => { c.addDependentCell(this) @@ -344,38 +383,41 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u } @tailrec - override final private[rasync] def addDependentCell(dependentCompleter: Cell[V, E]): Unit = state.get match { - case _: FinalState[V] => - // call now! - dependentCompleter.addUpdate(this) - - case current: IntermediateState[V, E] => - if (!current.dependers.contains(dependentCompleter)) { // ignore duplicates - val newState = new IntermediateState[V, E](current.res, current.tasksActive, current.dependees, current.dependers + dependentCompleter) - if (state.compareAndSet(current, newState)) { - - if (newState.res.value != updater.bottom) { - // if there has been a change in the past, call callback immediately - dependentCompleter.addUpdate(this) - } - } else addDependentCell(dependentCompleter) // try again - } - } - - /** - * Called by 'putNext' and 'putFinal'. It will try to join the current state - * with the new value by using the given updater and return the new value. - * If 'current == v' then it will return 'v'. - */ + override final private[rasync] def addDependentCell(dependentCompleter: Cell[V, E]): Unit = + state.get match { + case _: FinalState[V] => + // call now! + dependentCompleter.addUpdate(this) + + case current: IntermediateState[V, E] => + if (!current.dependers.contains(dependentCompleter)) { // ignore duplicates + val newState = new IntermediateState[V, E]( + current.res, + current.tasksActive, + current.dependees, + current.dependers + dependentCompleter + ) + if (state.compareAndSet(current, newState)) { + + if (newState.res.value != updater.bottom) { + // if there has been a change in the past, call callback immediately + dependentCompleter.addUpdate(this) + } + } else addDependentCell(dependentCompleter) // try again + } + } + + /** Called by 'putNext' and 'putFinal'. It will try to join the current state with the new value + * by using the given updater and return the new value. If 'current == v' then it will return + * 'v'. + */ private def tryJoin(current: V, next: V): V = { updater.update(current, next) } - /** - * Called by 'putNext' which will try creating a new state with some new value - * and then set the new state. The function returns 'true' if it succeeds, 'false' - * if it fails. - */ + /** Called by 'putNext' which will try creating a new state with some new value and then set the + * new state. The function returns 'true' if it succeeds, 'false' if it fails. + */ @tailrec private[rasync] final def tryNewState(value: V): Unit = state.get match { case _: FinalState[V] => // completed with final result already @@ -386,7 +428,12 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u // We only have to continue, if the updated value actually changes the cell; // If value is lower or equal current.res, nothing changes. if (updatedValue != current.res) { - val newState = new IntermediateState[V, E](updatedValue, current.tasksActive, current.dependees, current.dependers) + val newState = new IntermediateState[V, E]( + updatedValue, + current.tasksActive, + current.dependees, + current.dependers + ) if (state.compareAndSet(current, newState)) { // We have a new value. Inform dependers current.dependers.foreach(_.addUpdate(this)) @@ -395,29 +442,29 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u } } - private[rasync] override def tryComplete(value: Try[V], dontCall: Option[Seq[Cell[V, E]]]): Unit = state.get match { - case _: FinalState[V] => // completed with final result already - // As decided by phaller, we ignore all updates after freeze and do not throw exceptions + private[rasync] override def tryComplete(value: Try[V], dontCall: Option[Seq[Cell[V, E]]]): Unit = + state.get match { + case _: FinalState[V] => // completed with final result already + // As decided by phaller, we ignore all updates after freeze and do not throw exceptions - case current: IntermediateState[V, E] => // not completed + case current: IntermediateState[V, E] => // not completed + val updatedValue: Try[V] = value.map(tryJoin(current.res.value, _)) + val newState = new FinalState[V](updatedValue) + if (state.compareAndSet(current, newState)) { + // Cell has been completed successfully + // Other cells do not need to call us any more + current.dependees.keys.foreach(_.removeDependentCell(this)) - val updatedValue: Try[V] = value.map(tryJoin(current.res.value, _)) - val newState = new FinalState[V](updatedValue) - if (state.compareAndSet(current, newState)) { - // Cell has been completed successfully - // Other cells do not need to call us any more - current.dependees.keys.foreach(_.removeDependentCell(this)) - - // Inform all dependent cells, but not those that have been resolved in the same cycle. - val toCall = dontCall.map(current.dependers -- _).getOrElse(current.dependers) - toCall.foreach(_.addUpdate(this)) - - // for testing - onNextHandler.foreach(_.apply(updatedValue)) - onCompleteHandler.foreach(_.apply(updatedValue)) - nodepslatch.countDown() // we do not have any deps left - } else tryComplete(value, dontCall) // CAS failed, try again - } + // Inform all dependent cells, but not those that have been resolved in the same cycle. + val toCall = dontCall.map(current.dependers -- _).getOrElse(current.dependers) + toCall.foreach(_.addUpdate(this)) + + // for testing + onNextHandler.foreach(_.apply(updatedValue)) + onCompleteHandler.foreach(_.apply(updatedValue)) + nodepslatch.countDown() // we do not have any deps left + } else tryComplete(value, dontCall) // CAS failed, try again + } override private[rasync] def addUpdate(dependee: Cell[V, E]): Unit = state.get() match { case _: FinalState[V] => @@ -426,38 +473,48 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u current.dependees.get(dependee).foreach(_.addUpdate(dependee)) } - /** - * Remove `dependentCell` from the list of dependent cells. - * Afterwards, `dependentCell` won't get informed about updates of `this` cell any more. - */ + /** Remove `dependentCell` from the list of dependent cells. Afterwards, `dependentCell` won't get + * informed about updates of `this` cell any more. + */ @tailrec - override final private[rasync] def removeDependentCell(dependentCell: Cell[V, E]): Unit = state.get match { - case current: IntermediateState[V, E] => - val newDependers = current.dependers - dependentCell - val newState = new IntermediateState[V, E](current.res, current.tasksActive, current.dependees, newDependers) - if (!state.compareAndSet(current, newState)) { - removeDependentCell(dependentCell) - } - case _: FinalState[V] => /* we do not have any information stored anyway. */ - } - - /** - * Remove `dependentCell` from the list of dependent cells. - * Afterwards, `dependentCell` won't get informed about updates of `this` cell any more. - */ + override final private[rasync] def removeDependentCell(dependentCell: Cell[V, E]): Unit = + state.get match { + case current: IntermediateState[V, E] => + val newDependers = current.dependers - dependentCell + val newState = new IntermediateState[V, E]( + current.res, + current.tasksActive, + current.dependees, + newDependers + ) + if (!state.compareAndSet(current, newState)) { + removeDependentCell(dependentCell) + } + case _: FinalState[V] => /* we do not have any information stored anyway. */ + } + + /** Remove `dependentCell` from the list of dependent cells. Afterwards, `dependentCell` won't get + * informed about updates of `this` cell any more. + */ @tailrec - override private[rasync] final def removeDependeeCells(otherCells: Iterable[Cell[V, E]]): Unit = state.get match { - case current: IntermediateState[V, E] => + override private[rasync] final def removeDependeeCells(otherCells: Iterable[Cell[V, E]]): Unit = + state.get match { + case current: IntermediateState[V, E] => + + val newDependees = current.dependees -- otherCells // .filterNot(_ == otherCells) + val newState = new IntermediateState[V, E]( + current.res, + current.tasksActive, + newDependees, + current.dependers + ) + if (state.compareAndSet(current, newState)) { + if (newState.dependees.isEmpty) + nodepslatch.countDown() + } else removeDependeeCells(otherCells) - val newDependees = current.dependees -- otherCells //.filterNot(_ == otherCells) - val newState = new IntermediateState[V, E](current.res, current.tasksActive, newDependees, current.dependers) - if (state.compareAndSet(current, newState)) { - if (newState.dependees.isEmpty) - nodepslatch.countDown() - } else removeDependeeCells(otherCells) - - case _: FinalState[V] => /* we do not have any information stored anyway. */ - } + case _: FinalState[V] => /* we do not have any information stored anyway. */ + } override private[rasync] def waitUntilNoDeps(): Unit = { nodepslatch.await() @@ -468,22 +525,23 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u } override private[rasync] def tasksActive() = state.get match { - case _: FinalState[V] => false + case _: FinalState[V] => false case s: IntermediateState[_, _] => s.tasksActive } - /** - * Mark this cell as "running". - * - * @return Returns true, iff the cell's status changed (i.e. it had not been running before). - */ + /** Mark this cell as "running". + * + * @return + * Returns true, iff the cell's status changed (i.e. it had not been running before). + */ @tailrec override private[rasync] final def setTasksActive(): Boolean = state.get match { case current: IntermediateState[V, E] => if (current.tasksActive) false // Cell has been active before else { - val newState = new IntermediateState(current.res, true, current.dependees, current.dependers) + val newState = + new IntermediateState(current.res, true, current.dependees, current.dependers) if (!state.compareAndSet(current, newState)) setTasksActive() // update failed, retry else !current.tasksActive // return TRUE, iff previous value is FALSE } @@ -508,5 +566,17 @@ private[rasync] abstract class CellImpl[V, E >: Null](pool: HandlerPool[V, E], u } -private[rasync] class SequentialCellImpl[V, E >: Null](pool: HandlerPool[V, E], updater: Updater[V], override val init: (Cell[V, E]) => Outcome[V], override val entity: E) extends CellImpl[V, E](pool, updater, init, entity) with SequentialCell[V, E] -private[rasync] class ConcurrentCellImpl[V, E >: Null](pool: HandlerPool[V, E], updater: Updater[V], override val init: (Cell[V, E]) => Outcome[V], override val entity: E) extends CellImpl[V, E](pool, updater, init, entity) with ConcurrentCell[V, E] +private[rasync] class SequentialCellImpl[V, E >: Null]( + pool: HandlerPool[V, E], + updater: Updater[V], + override val init: (Cell[V, E]) => Outcome[V], + override val entity: E +) extends CellImpl[V, E](pool, updater, init, entity) + with SequentialCell[V, E] +private[rasync] class ConcurrentCellImpl[V, E >: Null]( + pool: HandlerPool[V, E], + updater: Updater[V], + override val init: (Cell[V, E]) => Outcome[V], + override val entity: E +) extends CellImpl[V, E](pool, updater, init, entity) + with ConcurrentCell[V, E] diff --git a/core/src/main/scala/com/phaller/rasync/cell/CellCompleter.scala b/core/src/main/scala/com/phaller/rasync/cell/CellCompleter.scala index f55af1c..9371143 100644 --- a/core/src/main/scala/com/phaller/rasync/cell/CellCompleter.scala +++ b/core/src/main/scala/com/phaller/rasync/cell/CellCompleter.scala @@ -5,36 +5,29 @@ import com.phaller.rasync.pool.HandlerPool import scala.util.{ Failure, Try } -/** - * Interface trait for programmatically completing a cell. Analogous to `Promise[V]`. - */ +/** Interface trait for programmatically completing a cell. Analogous to `Promise[V]`. + */ private[rasync] trait CellCompleter[V, E >: Null] { - /** - * The cell associated with this completer. - */ + /** The cell associated with this completer. + */ val cell: Cell[V, E] /** A method to call */ private[rasync] val init: (Cell[V, E]) => Outcome[V] - /** - * Update `this` cells value with `x` and freeze it. - * The new value of `this` cell is determined by its updater. - */ + /** Update `this` cells value with `x` and freeze it. The new value of `this` cell is determined + * by its updater. + */ def putFinal(x: V): Unit - /** - * Update `this` cells value with `x`. - * The new value of `this` cell is determined by its updater. - */ + /** Update `this` cells value with `x`. The new value of `this` cell is determined by its updater. + */ def putNext(x: V): Unit - /** - * Update `this` cells value with `x`. If `isFinal` is `true`, the - * cell will be frozen. - * The new value of `this` cell is determined by its updater. - */ + /** Update `this` cells value with `x`. If `isFinal` is `true`, the cell will be frozen. The new + * value of `this` cell is determined by its updater. + */ def put(x: V, isFinal: Boolean = false): Unit /** Complete the cell without changing its value. */ @@ -45,18 +38,20 @@ private[rasync] trait CellCompleter[V, E >: Null] { private[rasync] def tryNewState(value: V): Unit private[rasync] def tryComplete(value: Try[V], dontCall: Option[Seq[Cell[V, E]]]): Unit - /** - * Run code for `this` cell sequentially. - */ + /** Run code for `this` cell sequentially. + */ private[rasync] def sequential(f: () => _, prio: Int): Unit } object CellCompleter { - /** - * Create a completer for a cell holding values of type `V` - * given a `HandlerPool` and a `Key[V]`. - */ - def apply[V, E >: Null](init: (Cell[V, E]) => Outcome[V] = (_: Cell[V, E]) => NoOutcome, sequential: Boolean = false, entity: E = null)(implicit updater: Updater[V], pool: HandlerPool[V, E]): CellCompleter[V, E] = { + + /** Create a completer for a cell holding values of type `V` given a `HandlerPool` and a `Key[V]`. + */ + def apply[V, E >: Null]( + init: (Cell[V, E]) => Outcome[V] = (_: Cell[V, E]) => NoOutcome, + sequential: Boolean = false, + entity: E = null + )(implicit updater: Updater[V], pool: HandlerPool[V, E]): CellCompleter[V, E] = { val impl = if (sequential) new SequentialCellImpl[V, E](pool, updater, init, entity) else new ConcurrentCellImpl[V, E](pool, updater, init, entity) @@ -64,13 +59,15 @@ object CellCompleter { impl } - /** - * Create a cell completer which is already completed with value `result`. - * - * Note: there is no `K` type parameter, since we always use type - * `DefaultKey[V]`, no other key would make sense. - */ - def completed[V, E >: Null](result: V, entity: E = null)(implicit updater: Updater[V], pool: HandlerPool[V, E]): CellCompleter[V, E] = { + /** Create a cell completer which is already completed with value `result`. + * + * Note: there is no `K` type parameter, since we always use type `DefaultKey[V]`, no other key + * would make sense. + */ + def completed[V, E >: Null](result: V, entity: E = null)(implicit + updater: Updater[V], + pool: HandlerPool[V, E] + ): CellCompleter[V, E] = { val impl = new ConcurrentCellImpl[V, E](pool, updater, _ => NoOutcome, entity) pool.register(impl) impl.putFinal(result) diff --git a/core/src/main/scala/com/phaller/rasync/cell/callbackRunnable.scala b/core/src/main/scala/com/phaller/rasync/cell/callbackRunnable.scala index 066e6b0..3d4c1fe 100644 --- a/core/src/main/scala/com/phaller/rasync/cell/callbackRunnable.scala +++ b/core/src/main/scala/com/phaller/rasync/cell/callbackRunnable.scala @@ -6,25 +6,27 @@ import java.util.concurrent.atomic.AtomicReference import pool.HandlerPool import scala.annotation.tailrec -import scala.concurrent.OnCompleteRunnable +import scala.concurrent.Batchable import scala.util.{ Failure, Success, Try } import com.phaller.rasync.util.Counter -/** - * CallbackRunnables are tasks that need to be run, when a value of a cell changes, that - * some completer depends on. - * - * CallbackRunnables store information about the involved cells and the callback to - * be run. - */ -private[rasync] abstract class CallbackRunnable[V, E >: Null] extends Runnable with OnCompleteRunnable { +/** CallbackRunnables are tasks that need to be run, when a value of a cell changes, that some + * completer depends on. + * + * CallbackRunnables store information about the involved cells and the callback to be run. + */ +private[rasync] abstract class CallbackRunnable[V, E >: Null] + extends Runnable + with Batchable { protected val pool: HandlerPool[V, E] protected val dependentCompleter: CellCompleter[V, E] - /** The callback to be called. It retrieves an updated value of otherCell and returns an Outcome for dependentCompleter. */ - protected val callback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] ⇒ Outcome[V] + /** The callback to be called. It retrieves an updated value of otherCell and returns an Outcome + * for dependentCompleter. + */ + protected val callback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] protected val updatedDependees = new AtomicReference[Set[Cell[V, E]]](Set.empty) protected var prio: Int = Int.MaxValue @@ -40,7 +42,10 @@ private[rasync] abstract class CallbackRunnable[V, E >: Null] extends Runnable w // part of this update. // This computation of prio is not thread-safe but this does not matter for // priorities are no hard requirement anyway. - prio = Math.min(prio, pool.schedulingStrategy.calcPriority(dependentCompleter.cell, other, other.getState())) + prio = Math.min( + prio, + pool.schedulingStrategy.calcPriority(dependentCompleter.cell, other, other.getState()) + ) // The first incoming update (since the last execution) starts this runnable. // Other cells might still be added to updatedDependees concurrently, the runnable @@ -54,11 +59,10 @@ private[rasync] abstract class CallbackRunnable[V, E >: Null] extends Runnable w } else addUpdate(other) // retry } - /** - * Call the callback and update dependentCompleter according to the callback's result. - * This method is implemented by `ConcurrentCallbackRunnable` and `SequentialCalllbackRunnable`, - * where the latter implementation ensures that the callback is run sequentially. - */ + /** Call the callback and update dependentCompleter according to the callback's result. This + * method is implemented by `ConcurrentCallbackRunnable` and `SequentialCalllbackRunnable`, where + * the latter implementation ensures that the callback is run sequentially. + */ override def run(): Unit protected def callCallback(): Unit = { @@ -67,20 +71,20 @@ private[rasync] abstract class CallbackRunnable[V, E >: Null] extends Runnable w try { // Remove all updates from the list of updates that need to be handled – they will now be handled val dependees = updatedDependees.getAndSet(Set.empty) - val propagations = dependees.iterator.map(c ⇒ (c, c.getState())).toIterable + val propagations = dependees.iterator.map(c => (c, c.getState())).to(Iterable) val depsRemoved = // see below for depsRemoved callback(propagations) match { - case NextOutcome(v) ⇒ + case NextOutcome(v) => dependentCompleter.putNext(v) false - case FinalOutcome(v) ⇒ + case FinalOutcome(v) => dependentCompleter.putFinal(v) true - case FreezeOutcome ⇒ + case FreezeOutcome => dependentCompleter.freeze() true - case NoOutcome ⇒ + case NoOutcome => // Do not change the value of the cell // but remove all dependees that have had // a final value from the lists of dependees. @@ -91,33 +95,42 @@ private[rasync] abstract class CallbackRunnable[V, E >: Null] extends Runnable w // or a Failuare has been propagated, i.e. the dependee had been completed // and cannot change later if (!depsRemoved) { - val toRemove = propagations.iterator.filter({ - case (_, Success(NextOutcome(_))) ⇒ false - case _ ⇒ true - }).map(_._1).toIterable + val toRemove = propagations.iterator + .filter({ + case (_, Success(NextOutcome(_))) => false + case _ => true + }) + .map(_._1) + .to(Iterable) dependentCompleter.cell.removeDependeeCells(toRemove) } } catch { // An exception thrown in a callback is stored as the final value for the depender - case e: Exception ⇒ + case e: Exception => dependentCompleter.putFailure(Failure(e)) } } } } -/** - * Run a callback concurrently, if a value in a cell changes. - */ -private[rasync] class ConcurrentCallbackRunnable[V, E >: Null](override val pool: HandlerPool[V, E], override val dependentCompleter: CellCompleter[V, E], override val callback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] ⇒ Outcome[V]) extends CallbackRunnable[V, E] { +/** Run a callback concurrently, if a value in a cell changes. + */ +private[rasync] class ConcurrentCallbackRunnable[V, E >: Null]( + override val pool: HandlerPool[V, E], + override val dependentCompleter: CellCompleter[V, E], + override val callback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] +) extends CallbackRunnable[V, E] { override def run(): Unit = callCallback() } -/** - * Run a callback sequentially (for a dependent cell), if a value in another cell changes. - */ -private[rasync] class SequentialCallbackRunnable[V, E >: Null](override val pool: HandlerPool[V, E], override val dependentCompleter: CellCompleter[V, E], override val callback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] ⇒ Outcome[V]) extends CallbackRunnable[V, E] { +/** Run a callback sequentially (for a dependent cell), if a value in another cell changes. + */ +private[rasync] class SequentialCallbackRunnable[V, E >: Null]( + override val pool: HandlerPool[V, E], + override val dependentCompleter: CellCompleter[V, E], + override val callback: Iterable[(Cell[V, E], Try[ValueOutcome[V]])] => Outcome[V] +) extends CallbackRunnable[V, E] { override def run(): Unit = dependentCompleter.sequential(callCallback _, prio) } diff --git a/core/src/main/scala/com/phaller/rasync/cell/outcome.scala b/core/src/main/scala/com/phaller/rasync/cell/outcome.scala index 597dd73..9e07c00 100644 --- a/core/src/main/scala/com/phaller/rasync/cell/outcome.scala +++ b/core/src/main/scala/com/phaller/rasync/cell/outcome.scala @@ -1,19 +1,21 @@ package com.phaller.rasync.cell -/** - * Use this trait in callbacks to return the new value of a cell. - * `NextOutcome(v)` and `FinalOutcome(v)` put the value `v` into - * the cell; in the latter case the cell is completed. - * Use `NoOutcome` to indicate that no progress is possible. - */ +/** Use this trait in callbacks to return the new value of a cell. `NextOutcome(v)` and + * `FinalOutcome(v)` put the value `v` into the cell; in the latter case the cell is completed. Use + * `NoOutcome` to indicate that no progress is possible. + */ sealed trait Outcome[+V] sealed trait FreezeOutcome[+V] extends Outcome[V] sealed trait IntermediateOutcome[+V] extends Outcome[V] sealed trait ValueOutcome[+V] extends Outcome[V] { val value: V } -final case class NextOutcome[+V](override val value: V) extends ValueOutcome[V] with IntermediateOutcome[V] -final case class FinalOutcome[+V](override val value: V) extends ValueOutcome[V] with FreezeOutcome[V] +final case class NextOutcome[+V](override val value: V) + extends ValueOutcome[V] + with IntermediateOutcome[V] +final case class FinalOutcome[+V](override val value: V) + extends ValueOutcome[V] + with FreezeOutcome[V] case object NoOutcome extends IntermediateOutcome[Nothing] case object FreezeOutcome extends FreezeOutcome[Nothing] @@ -29,44 +31,49 @@ object Outcome { if (e._2) FinalOutcome(e._1) else NextOutcome(e._1) - /** - * Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. - * - * If `valueOpt` is `None`, `NoOutcome` is returned. Otherwise, a `NextOutcome` or - * `FinalOutcome` is returned depending on `isFinal`. - * - * @param valueOpt Option of a new value. - * @param isFinal Indicates if the value is final. - * @return Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. - */ + /** Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. + * + * If `valueOpt` is `None`, `NoOutcome` is returned. Otherwise, a `NextOutcome` or `FinalOutcome` + * is returned depending on `isFinal`. + * + * @param valueOpt + * Option of a new value. + * @param isFinal + * Indicates if the value is final. + * @return + * Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. + */ def apply[V](valueOpt: Option[V], isFinal: Boolean): Outcome[V] = - valueOpt.map(value => if (isFinal) FinalOutcome(value) else NextOutcome(value)).getOrElse(NoOutcome) + valueOpt + .map(value => if (isFinal) FinalOutcome(value) else NextOutcome(value)) + .getOrElse(NoOutcome) - /** - * Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. - * - * If `valueOpt` is `None`, `NoOutcome` is returned. Otherwise, a `NextOutcome` or - * `FinalOutcome` is returned depending on the boolean parameter. - * - * @param valueOpt Option of a new value, a pair of a value V and a boolean to indicate if it is final. - * @return Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. - */ + /** Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. + * + * If `valueOpt` is `None`, `NoOutcome` is returned. Otherwise, a `NextOutcome` or `FinalOutcome` + * is returned depending on the boolean parameter. + * + * @param valueOpt + * Option of a new value, a pair of a value V and a boolean to indicate if it is final. + * @return + * Returns a `NextOutcome`, `FinalOutcome`, or `NoOutcome` object. + */ def apply[V](valueOpt: Option[(V, Boolean)]): Outcome[V] = valueOpt match { case Some((v, false)) => NextOutcome(v) - case Some((v, true)) => FinalOutcome(v) - case None => NoOutcome + case Some((v, true)) => FinalOutcome(v) + case None => NoOutcome } /** Match outcomes. */ def unapply[V](outcome: Outcome[V]): Option[(V, Boolean)] = outcome match { case FinalOutcome(v) => Some(v, true) - case NextOutcome(v) => Some(v, false) - case _ => None + case NextOutcome(v) => Some(v, false) + case _ => None } /** Match non-empty outcomes. */ def unapply[V](outcome: ValueOutcome[V]): (V, Boolean) = outcome match { case FinalOutcome(v) => (v, true) - case NextOutcome(v) => (v, false) + case NextOutcome(v) => (v, false) } } diff --git a/core/src/main/scala/com/phaller/rasync/lattice/Lattice.scala b/core/src/main/scala/com/phaller/rasync/lattice/Lattice.scala index fa51b45..ee92f0a 100644 --- a/core/src/main/scala/com/phaller/rasync/lattice/Lattice.scala +++ b/core/src/main/scala/com/phaller/rasync/lattice/Lattice.scala @@ -4,12 +4,10 @@ package lattice import scala.annotation.implicitNotFound trait PartialOrderingWithBottom[V] extends PartialOrdering[V] { - /** - * Result of comparing x with operand y. Returns None if operands are not comparable. If operands are comparable, returns Some(r) where - * r < 0 iff x < y - * r == 0 iff x == y - * r > 0 iff x > y - */ + + /** Result of comparing x with operand y. Returns None if operands are not comparable. If operands + * are comparable, returns Some(r) where r < 0 iff x < y r == 0 iff x == y r > 0 iff x > y + */ override def tryCompare(x: V, y: V): Option[Int] = if (lt(x, y)) Some(-1) else if (gt(x, y)) Some(1) @@ -31,9 +29,9 @@ object PartialOrderingWithBottom { @implicitNotFound("type ${V} does not have a Lattice instance") trait Lattice[V] extends PartialOrderingWithBottom[V] { - /** - * Return the join of v1 and v2 wrt. the lattice. - */ + + /** Return the join of v1 and v2 wrt. the lattice. + */ def join(v1: V, v2: V): V override def lteq(v1: V, v2: V): Boolean = { diff --git a/core/src/main/scala/com/phaller/rasync/lattice/updater.scala b/core/src/main/scala/com/phaller/rasync/lattice/updater.scala index b3867d9..9c772dd 100644 --- a/core/src/main/scala/com/phaller/rasync/lattice/updater.scala +++ b/core/src/main/scala/com/phaller/rasync/lattice/updater.scala @@ -1,38 +1,36 @@ package com.phaller.rasync package lattice -/** - * An updater defines, how to react to a value that is being put to a cell. - * Given a `current` value of the cell a a `next` value, the `update` method - * returns the new value of the cell of a "combination" of `current` and `next`. - * - * The `bottom` value of an updater defines, what the initial value of a cell is. - */ +/** An updater defines, how to react to a value that is being put to a cell. Given a `current` value + * of the cell a a `next` value, the `update` method returns the new value of the cell of a + * "combination" of `current` and `next`. + * + * The `bottom` value of an updater defines, what the initial value of a cell is. + */ trait Updater[V] { val bottom: V def update(current: V, next: V): V } -/** - * AggregationUpdaters are built on lattices and compute new values as `join` of - * all incoming ("next") values. Therefore, AggregationUpdaters do not throw exceptions - * as the `join` of two lattice values is always defined. - * - * The initial value is the bottom value of the lattice. - */ +/** AggregationUpdaters are built on lattices and compute new values as `join` of all incoming + * ("next") values. Therefore, AggregationUpdaters do not throw exceptions as the `join` of two + * lattice values is always defined. + * + * The initial value is the bottom value of the lattice. + */ class AggregationUpdater[V](val lattice: Lattice[V]) extends Updater[V] { override val bottom: V = lattice.bottom override def update(current: V, next: V): V = lattice.join(current, next) } -/** - * MonotonicUpdaters are built on partial orderings. The incoming ("next") value is - * used as the new value for the cell, as long as the update is monotonic. - * Otherwise a NotMonotonicException is thrown. - * - * The initial value is the bottom value of the partial ordering. - */ -class MonotonicUpdater[V](val partialOrderingWithBottom: PartialOrderingWithBottom[V]) extends Updater[V] { +/** MonotonicUpdaters are built on partial orderings. The incoming ("next") value is used as the new + * value for the cell, as long as the update is monotonic. Otherwise a NotMonotonicException is + * thrown. + * + * The initial value is the bottom value of the partial ordering. + */ +class MonotonicUpdater[V](val partialOrderingWithBottom: PartialOrderingWithBottom[V]) + extends Updater[V] { override val bottom: V = partialOrderingWithBottom.bottom override def update(current: V, next: V): V = @@ -46,7 +44,9 @@ object Updater { new AggregationUpdater[T](lattice) // convert a lattice to its canonical monotonic updater - def partialOrderingToUpdater[T](implicit partialOrderingWithBottom: PartialOrderingWithBottom[T]): Updater[T] = + def partialOrderingToUpdater[T](implicit + partialOrderingWithBottom: PartialOrderingWithBottom[T] + ): Updater[T] = new MonotonicUpdater[T](partialOrderingWithBottom) // create an updater for pairs of values. @@ -55,9 +55,12 @@ object Updater { (updater.update(current._1, next._1), updater.update(current._2, next._2)) override val bottom: (T, T) = - (updater.asInstanceOf[PartialOrderingWithBottom[T]].bottom, updater.asInstanceOf[PartialOrderingWithBottom[T]].bottom) + ( + updater.asInstanceOf[PartialOrderingWithBottom[T]].bottom, + updater.asInstanceOf[PartialOrderingWithBottom[T]].bottom + ) } } -final case class NotMonotonicException[D](current: D, next: D) extends IllegalStateException( - s"Violation of ordering with current $current and next $next!") +final case class NotMonotonicException[D](current: D, next: D) + extends IllegalStateException(s"Violation of ordering with current $current and next $next!") diff --git a/core/src/main/scala/com/phaller/rasync/pool/HandlerPool.scala b/core/src/main/scala/com/phaller/rasync/pool/HandlerPool.scala index c987262..f1b2a29 100644 --- a/core/src/main/scala/com/phaller/rasync/pool/HandlerPool.scala +++ b/core/src/main/scala/com/phaller/rasync/pool/HandlerPool.scala @@ -9,8 +9,8 @@ import lattice.{ DefaultKey, Key, Updater } import org.opalj.graphs._ import scala.annotation.tailrec -import scala.collection.JavaConverters._ import scala.concurrent.{ Future, Promise } +import scala.jdk.CollectionConverters._ import scala.util.{ Failure, Success } import scala.util.control.NonFatal @@ -22,15 +22,23 @@ private class PoolState(val handlers: List[() => Unit] = List(), val submittedTa } class HandlerPool[V, E >: Null]( - val key: Key[V, E] = new DefaultKey[V, E](), - val parallelism: Int = Runtime.getRuntime.availableProcessors(), - unhandledExceptionHandler: Throwable => Unit = _.printStackTrace(), - val schedulingStrategy: SchedulingStrategy[V, E] = new DefaultScheduling[V, E]) { + val key: Key[V, E] = new DefaultKey[V, E](), + val parallelism: Int = Runtime.getRuntime.availableProcessors(), + unhandledExceptionHandler: Throwable => Unit = _.printStackTrace(), + val schedulingStrategy: SchedulingStrategy[V, E] = new DefaultScheduling[V, E] +) { private val pool: AbstractExecutorService = schedulingStrategy match { case _: DefaultScheduling[_, _] => new ForkJoinPool(parallelism) - case _ => new ThreadPoolExecutor(parallelism, parallelism, Int.MaxValue, TimeUnit.NANOSECONDS, new PriorityBlockingQueue[Runnable]()) + case _ => + new ThreadPoolExecutor( + parallelism, + parallelism, + Int.MaxValue, + TimeUnit.NANOSECONDS, + new PriorityBlockingQueue[Runnable]() + ) } private val poolState = new AtomicReference[PoolState](new PoolState) @@ -41,58 +49,64 @@ class HandlerPool[V, E >: Null]( override def compareTo(t: Runnable): Int = { val p = t match { case runnable: PriorityRunnable => runnable.priority - case _ => 1 + case _ => 1 } priority - p } } - /** - * Returns a new cell in this HandlerPool. - * - * Creates a new cell with the given key. The `init` method is used to - * determine an initial value for that cell and to set up dependencies via `whenNext`. - * It gets called, when the cell is awaited, either directly by the triggerExecution method - * of the HandlerPool or if a cell that depends on this cell is awaited. - * - * @param init A callback to return the initial value for this cell and to set up dependencies. - * @param updater The updater used to update the value of this cell. - * @return Returns a cell. - */ - def mkCell(init: (Cell[V, E]) => Outcome[V], entity: E = null)(implicit updater: Updater[V]): Cell[V, E] = { + /** Returns a new cell in this HandlerPool. + * + * Creates a new cell with the given key. The `init` method is used to determine an initial value + * for that cell and to set up dependencies via `whenNext`. It gets called, when the cell is + * awaited, either directly by the triggerExecution method of the HandlerPool or if a cell that + * depends on this cell is awaited. + * + * @param init + * A callback to return the initial value for this cell and to set up dependencies. + * @param updater + * The updater used to update the value of this cell. + * @return + * Returns a cell. + */ + def mkCell(init: (Cell[V, E]) => Outcome[V], entity: E = null)(implicit + updater: Updater[V] + ): Cell[V, E] = { val impl = new ConcurrentCellImpl[V, E](this, updater, init, entity) this.register(impl) impl } - def mkSequentialCell(init: (Cell[V, E]) => Outcome[V], entity: E = null)(implicit updater: Updater[V]): Cell[V, E] = { + def mkSequentialCell(init: (Cell[V, E]) => Outcome[V], entity: E = null)(implicit + updater: Updater[V] + ): Cell[V, E] = { val impl = new SequentialCellImpl[V, E](this, updater, init, entity) this.register(impl) impl } - /** - * Returns a new cell in this HandlerPool. - * - * Creates a new, completed cell with value `v`. - * - * @param updater The updater used to update the value of this cell. - * @return Returns a cell with value `v`. - */ + /** Returns a new cell in this HandlerPool. + * + * Creates a new, completed cell with value `v`. + * + * @param updater + * The updater used to update the value of this cell. + * @return + * Returns a cell with value `v`. + */ def mkCompletedCell(result: V, entity: E)(implicit updater: Updater[V]): Cell[V, E] = { CellCompleter.completed(result, entity)(updater, this).cell } - /** - * Add an event handler that is called, when the pool reaches quiescence. - * - * The `handler` is called once after the pool reaches quiescence the first time - * after it has been added. - */ + /** Add an event handler that is called, when the pool reaches quiescence. + * + * The `handler` is called once after the pool reaches quiescence the first time after it has + * been added. + */ @tailrec final def onQuiescent(handler: () => Unit): Unit = { val state = poolState.get() - if (state.isQuiescent) { + if (state.isQuiescent()) { execute(new Runnable { def run(): Unit = handler() }, 0) } else { val newState = new PoolState(handler :: state.handlers, state.submittedTasks) @@ -102,33 +116,32 @@ class HandlerPool[V, E >: Null]( } } - /** - * Register a cell with this HandlerPool. - * - * @param cell The cell. - */ + /** Register a cell with this HandlerPool. + * + * @param cell + * The cell. + */ def register(cell: Cell[V, E]): Unit = cellsNotDone.add(cell) - /** - * Deregister a cell from this HandlerPool. - * - * @param cell The cell. - */ + /** Deregister a cell from this HandlerPool. + * + * @param cell + * The cell. + */ def deregister(cell: Cell[V, E]): Unit = cellsNotDone.remove(cell) - /** - * Remove all completed cells from cellsNotDone. Cells are not removed on deregister, but when the queue is - * queried. - */ + /** Remove all completed cells from cellsNotDone. Cells are not removed on deregister, but when + * the queue is queried. + */ private def deregisterCompletedCells(): Unit = { cellsNotDone.removeIf(_.isComplete) } /** Returns all non-completed cells, when quiescence is reached. */ def quiescentIncompleteCells: Future[Iterable[Cell[_, _]]] = { - val p = Promise[Iterable[Cell[_, _]]] + val p = Promise[Iterable[Cell[_, _]]]() this.onQuiescent { () => deregisterCompletedCells() p.success(cellsNotDone.asScala) @@ -136,19 +149,18 @@ class HandlerPool[V, E >: Null]( p.future } - /** - * Wait for a quiescent state. - * Afterwards, resolve all cells without dependencies with the respective - * `fallback` value calculated by it's `Key`. - * Also, resolve cycles without dependencies (cSCCs) using the respective `Key`'s `resolve` method. - * Both might lead to computations on other cells being triggered. - * If more cells are unresolved, recursively wait for resolution. - * - * @return The future is set once the resolve is finished and the quiescent state is reached. - * The boolean parameter indicates if cycles have been resolved or not. - */ + /** Wait for a quiescent state. Afterwards, resolve all cells without dependencies with the + * respective `fallback` value calculated by it's `Key`. Also, resolve cycles without + * dependencies (cSCCs) using the respective `Key`'s `resolve` method. Both might lead to + * computations on other cells being triggered. If more cells are unresolved, recursively wait + * for resolution. + * + * @return + * The future is set once the resolve is finished and the quiescent state is reached. The + * boolean parameter indicates if cycles have been resolved or not. + */ def quiescentResolveCell: Future[Unit] = { - val p = Promise[Unit] + val p = Promise[Unit]() this.onQuiescent { () => deregisterCompletedCells() @@ -168,7 +180,8 @@ class HandlerPool[V, E >: Null]( if (activeCells.isEmpty) { false } else { - val cSCCs = closedSCCs[Cell[V, E]](activeCells, (cell: Cell[V, E]) => cell.cellDependencies) + val cSCCs = + closedSCCs[Cell[V, E]](activeCells, (cell: Cell[V, E]) => cell.cellDependencies) cSCCs .map(resolveCycle) // resolve all cSCCs .exists(b => b) // return if any resolution took place @@ -186,32 +199,36 @@ class HandlerPool[V, E >: Null]( p.future } - /** - * Resolves a cycle of unfinished cells via the key's `resolve` method. - */ + /** Resolves a cycle of unfinished cells via the key's `resolve` method. + */ private def resolveCycle(cells: Iterable[Cell[V, E]]): Boolean = resolve(cells, key.resolve) - /** - * Resolves a cell with default value with the key's `fallback` method. - */ + /** Resolves a cell with default value with the key's `fallback` method. + */ private def resolveIndependent(cells: Iterable[Cell[V, E]]): Boolean = resolve(cells, key.fallback) /** Resolve all cells with the associated value. */ - private def resolve(cells: Iterable[Cell[V, E]], k: (Iterable[Cell[V, E]]) => Iterable[(Cell[V, E], V)]): Boolean = { + private def resolve( + cells: Iterable[Cell[V, E]], + k: (Iterable[Cell[V, E]]) => Iterable[(Cell[V, E], V)] + ): Boolean = { try { val results = k(cells) val dontCall = results.map(_._1).toSeq for ((c, v) <- results) { val res = Success(v) - execute(new Runnable { - override def run(): Unit = { - // resolve each cell with the given value - // but do not propagate among the cells in the same set (i.e. the same cSCC) - c.resolveWithValue(res, dontCall) - } - }, schedulingStrategy.calcPriority(c, res)) + execute( + new Runnable { + override def run(): Unit = { + // resolve each cell with the given value + // but do not propagate among the cells in the same set (i.e. the same cSCC) + c.resolveWithValue(res, dontCall) + } + }, + schedulingStrategy.calcPriority(c, res) + ) } results.nonEmpty } catch { @@ -220,15 +237,14 @@ class HandlerPool[V, E >: Null]( val f = Failure(e) val dontCall = cells.toSeq cells.foreach(c => - execute(() => c.resolveWithValue(f, dontCall), schedulingStrategy.calcPriority(c, f))) + execute(() => c.resolveWithValue(f, dontCall), schedulingStrategy.calcPriority(c, f)) + ) cells.nonEmpty } } - /** - * Increase the number of submitted tasks. - * Change the PoolState accordingly. - */ + /** Increase the number of submitted tasks. Change the PoolState accordingly. + */ private def incSubmittedTasks(): Unit = { var submitSuccess = false while (!submitSuccess) { @@ -238,10 +254,9 @@ class HandlerPool[V, E >: Null]( } } - /** - * Decrease the number of submitted tasks and run registered handlers, if quiescent. - * Change the PoolState accordingly. - */ + /** Decrease the number of submitted tasks and run registered handlers, if quiescent. Change the + * PoolState accordingly. + */ private def decSubmittedTasks(i: Int = 1): Unit = { var success = false var handlersToRun: Option[List[() => Unit]] = None @@ -266,15 +281,18 @@ class HandlerPool[V, E >: Null]( // run all handler that have been attached at the time quiescence was reached if (handlersToRun.nonEmpty) { handlersToRun.get.foreach { handler => - execute(new Runnable { - def run(): Unit = handler() - }, 0) // TODO set a priority + execute( + new Runnable { + def run(): Unit = handler() + }, + 0 + ) // TODO set a priority } } } // Shouldn't we use: - //def execute(f : => Unit) : Unit = + // def execute(f : => Unit) : Unit = // execute(new Runnable{def run() : Unit = f}) def execute(fun: () => Unit, priority: Int): Unit = @@ -307,42 +325,41 @@ class HandlerPool[V, E >: Null]( } } - /** - * If a cell is triggered, it's `init` method is - * run to both get an initial (or possibly final) value - * and to set up dependencies (via whenNext/whenComplete). - * All dependees automatically get triggered. - * - * @param cell The cell that is triggered. - */ + /** If a cell is triggered, it's `init` method is run to both get an initial (or possibly final) + * value and to set up dependencies (via whenNext/whenComplete). All dependees automatically get + * triggered. + * + * @param cell + * The cell that is triggered. + */ private[rasync] def triggerExecution(cell: Cell[V, E], priority: Int = 0): Unit = { if (cell.setTasksActive()) // if the cell's state has successfully been changed, schedule further computations - execute(() => { - val completer = cell.completer - try { - val outcome = completer.init(cell) // call the init method - outcome match { - case Outcome(v, isFinal) => completer.put(v, isFinal) - case NoOutcome => /* don't do anything */ + execute( + () => { + val completer = cell.completer + try { + val outcome = completer.init(cell) // call the init method + outcome match { + case Outcome(v, isFinal) => completer.put(v, isFinal) + case NoOutcome => /* don't do anything */ + } + } catch { + case e: Exception => completer.putFailure(Failure(e)) } - } catch { - case e: Exception => completer.putFailure(Failure(e)) - } - }, priority) + }, + priority + ) } - /** - * Possibly initiates an orderly shutdown in which previously - * submitted tasks are executed, but no new tasks are accepted. - * This method should only be called, when the pool is quiescent. - */ + /** Possibly initiates an orderly shutdown in which previously submitted tasks are executed, but + * no new tasks are accepted. This method should only be called, when the pool is quiescent. + */ def shutdown(): Unit = pool.shutdown() - /** - * Waits for quiescence, then shuts the pool down. - */ + /** Waits for quiescence, then shuts the pool down. + */ def onQuiescenceShutdown(): Unit = this.onQuiescent(() => pool.shutdown()) diff --git a/core/src/main/scala/com/phaller/rasync/pool/scheduldingStrategy.scala b/core/src/main/scala/com/phaller/rasync/pool/scheduldingStrategy.scala index 44dad7f..701c67e 100644 --- a/core/src/main/scala/com/phaller/rasync/pool/scheduldingStrategy.scala +++ b/core/src/main/scala/com/phaller/rasync/pool/scheduldingStrategy.scala @@ -5,25 +5,20 @@ import com.phaller.rasync.cell.ValueOutcome import scala.util.Try -/** - * A scheduling strategy defines priorities for dependency callbacks - * and cell resolution. - * - * Whenever a dependency is triggered due to a change of a dependee cell, - * a task is added to the handler pool. Those tasks will eventually be picked - * up and executed, potentielly concurrently to other tasks. - * Each task added to the pool is assigned a priority as defined by a - * SchedulingStrategy, where tasks with lower priorities are more likely - * to be executed earlier. (Note that due to concurrent execution there is - * no guarantee for any order of execution. Use sequential callbacks to - * avoid concurrent execution of certain tasks.) - * - * If a tasks has been created because of a change in a dependee cell `other` being propgated to - * a `dependentCell`, `SchedulingStrategy.calcPriority(dependentCell, other)` is called to - * obtain a priority. - * If a tasks has been created to complete a `cell` via a `Key`, - * `SchedulingStrategy.calcPriority(cell)` is called. - */ +/** A scheduling strategy defines priorities for dependency callbacks and cell resolution. + * + * Whenever a dependency is triggered due to a change of a dependee cell, a task is added to the + * handler pool. Those tasks will eventually be picked up and executed, potentielly concurrently to + * other tasks. Each task added to the pool is assigned a priority as defined by a + * SchedulingStrategy, where tasks with lower priorities are more likely to be executed earlier. + * (Note that due to concurrent execution there is no guarantee for any order of execution. Use + * sequential callbacks to avoid concurrent execution of certain tasks.) + * + * If a tasks has been created because of a change in a dependee cell `other` being propgated to a + * `dependentCell`, `SchedulingStrategy.calcPriority(dependentCell, other)` is called to obtain a + * priority. If a tasks has been created to complete a `cell` via a `Key`, + * `SchedulingStrategy.calcPriority(cell)` is called. + */ trait SchedulingStrategy[V, E >: Null] { def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int def calcPriority(dependentCell: Cell[V, E], value: Try[V]): Int @@ -38,12 +33,20 @@ object SchedulingStrategy { /** All tasks are of equal priority. */ class DefaultScheduling[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = 0 + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = 0 override def calcPriority(cell: Cell[V, E], value: Try[V]): Int = 0 } class SourcesWithManyTargetsFirst[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = -other.numDependentCells override def calcPriority(cell: Cell[V, E], value: Try[V]): Int = @@ -51,7 +54,11 @@ class SourcesWithManyTargetsFirst[V, E >: Null] extends SchedulingStrategy[V, E] } class SourcesWithManyTargetsLast[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = other.numDependentCells override def calcPriority(cell: Cell[V, E], value: Try[V]): Int = @@ -59,7 +66,11 @@ class SourcesWithManyTargetsLast[V, E >: Null] extends SchedulingStrategy[V, E] } class SourcesWithManySourcesFirst[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = -other.numDependencies override def calcPriority(cell: Cell[V, E], value: Try[V]): Int = @@ -67,7 +78,11 @@ class SourcesWithManySourcesFirst[V, E >: Null] extends SchedulingStrategy[V, E] } class SourcesWithManySourcesLast[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = other.numDependencies override def calcPriority(cell: Cell[V, E], value: Try[V]): Int = @@ -75,28 +90,44 @@ class SourcesWithManySourcesLast[V, E >: Null] extends SchedulingStrategy[V, E] } class TargetsWithManySourcesFirst[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = -dependentCell.numDependencies override def calcPriority(cell: Cell[V, E], value: Try[V]): Int = 0 } class TargetsWithManySourcesLast[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = dependentCell.numDependencies override def calcPriority(dependentCell: Cell[V, E], value: Try[V]): Int = 0 } class TargetsWithManyTargetsFirst[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = -dependentCell.numDependencies override def calcPriority(dependentCell: Cell[V, E], value: Try[V]): Int = 0 } class TargetsWithManyTargetsLast[V, E >: Null] extends SchedulingStrategy[V, E] { - override def calcPriority(dependentCell: Cell[V, E], other: Cell[V, E], value: Try[ValueOutcome[V]]): Int = + override def calcPriority( + dependentCell: Cell[V, E], + other: Cell[V, E], + value: Try[ValueOutcome[V]] + ): Int = dependentCell.numDependentCells override def calcPriority(dependentCell: Cell[V, E], value: Try[V]): Int = 0 diff --git a/core/src/main/scala/com/phaller/rasync/util/Counter.scala b/core/src/main/scala/com/phaller/rasync/util/Counter.scala index 56a3a67..0c1b579 100644 --- a/core/src/main/scala/com/phaller/rasync/util/Counter.scala +++ b/core/src/main/scala/com/phaller/rasync/util/Counter.scala @@ -29,9 +29,12 @@ object Counter { val counters = s"\tCounters\n" + f"\t\t${"Number"}%10s ${"Name"}%-90s\n" + f"\t\t${"------"}%10s ${"----"}%-90s\n" + - profilingCounter.toSeq.sortBy(x ⇒ x._1).map { x ⇒ - f"\t\t${x._2.get}%10s ${x._1}%-90s\n" - }.mkString + profilingCounter.toSeq + .sortBy(x => x._1) + .map { x => + f"\t\t${x._2.get}%10s ${x._1}%-90s\n" + } + .mkString counters } diff --git a/core/src/test/scala/com/phaller/rasync/test/Backoff.scala b/core/src/test/scala/com/phaller/rasync/test/Backoff.scala index 804e77f..da057bb 100644 --- a/core/src/test/scala/com/phaller/rasync/test/Backoff.scala +++ b/core/src/test/scala/com/phaller/rasync/test/Backoff.scala @@ -60,7 +60,8 @@ private object Random { seed } def scale(seed: Long, max: Int): Int = { - if (max <= 0) max else { + if (max <= 0) max + else { val r = (seed % max).toInt if (r < 0) -r else r } diff --git a/core/src/test/scala/com/phaller/rasync/test/BaseSuite.scala b/core/src/test/scala/com/phaller/rasync/test/BaseSuite.scala index eeec7ac..e89d22f 100644 --- a/core/src/test/scala/com/phaller/rasync/test/BaseSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/BaseSuite.scala @@ -19,10 +19,14 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { implicit val intUpdater: Updater[Int] = new IntUpdater - def if10thenFinal20[E >: Null](updates: Iterable[(Cell[Int, E], Try[ValueOutcome[Int]])]): Outcome[Int] = + def if10thenFinal20[E >: Null]( + updates: Iterable[(Cell[Int, E], Try[ValueOutcome[Int]])] + ): Outcome[Int] = ifXthenFinalY[E](10, 20)(updates) - def ifXthenFinalY[E >: Null](x: Int, y: Int)(upd: Iterable[(Cell[Int, E], Try[ValueOutcome[Int]])]): Outcome[Int] = { + def ifXthenFinalY[E >: Null](x: Int, y: Int)( + upd: Iterable[(Cell[Int, E], Try[ValueOutcome[Int]])] + ): Outcome[Int] = { val c = upd.head._2 if (c.get.value == x) FinalOutcome(y) else NoOutcome } @@ -77,7 +81,7 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { assert(true) } catch { case ise: IllegalStateException => assert(false) - case e: Exception => assert(false) + case e: Exception => assert(false) } pool.onQuiescenceShutdown() @@ -324,7 +328,7 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { completer1.cell.when(completer2.cell)(_.head._2 match { case Success(NextOutcome(Mutable)) => NextOutcome(Mutable) - case _ => NoOutcome + case _ => NoOutcome }) completer1.putFinal(Immutable) @@ -370,9 +374,11 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { val cell1 = completer1.cell cell1.trigger() - pool.execute(() => cell1.when(completer2.cell)(_ => { - NoOutcome - })) + pool.execute(() => + cell1.when(completer2.cell)(_ => { + NoOutcome + }) + ) pool.execute(() => completer2.putFinal(Mutable)) @@ -392,10 +398,12 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { for (_ <- 1 to 1000) { val completer1 = mkCompleter[Immutability, Null] val completer2 = mkCompleter[Immutability, Null] - completer1.cell.when(completer2.cell)(it => it.head._2.get.value match { - case Immutable | ConditionallyImmutable => NoOutcome - case Mutable => NextOutcome(Mutable) - }) + completer1.cell.when(completer2.cell)(it => + it.head._2.get.value match { + case Immutable | ConditionallyImmutable => NoOutcome + case Mutable => NextOutcome(Mutable) + } + ) assert(completer1.cell.numDependencies == 1) @@ -422,10 +430,12 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { val cell1 = completer1.cell - pool.execute(() => cell1.when(completer2.cell)(it => { - if (it.head._2.get.value == Mutable) NextOutcome(Mutable) - else NoOutcome - })) + pool.execute(() => + cell1.when(completer2.cell)(it => { + if (it.head._2.get.value == Mutable) NextOutcome(Mutable) + else NoOutcome + }) + ) pool.execute(() => completer2.putNext(Mutable)) val fut = pool.quiescentResolveCell @@ -468,7 +478,7 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { completer1.putNext(50) } catch { case _: IllegalStateException => assert(false) - case _: Exception => assert(false) + case _: Exception => assert(false) } pool.onQuiescenceShutdown() @@ -552,7 +562,7 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { val x = it.head._2 x.get match { case FinalOutcome(_) => x.get // complete, if completer2 is completed - case _ => NoOutcome + case _ => NoOutcome } }) @@ -857,13 +867,13 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { } val key = DefaultKey[Int] - implicit val pool = new HandlerPool[Int, Null](key, unhandledExceptionHandler = { t => /* do nothing */ }) + implicit val pool = + new HandlerPool[Int, Null](key, unhandledExceptionHandler = { t => /* do nothing */ }) val completer = mkCompleter[Int, Null] pool.execute { () => // NOTE: This will print a stacktrace, but that is fine (not a bug). - throw new Exception( - "Even if this happens, quiescent handlers should still run.") + throw new Exception("Even if this happens, quiescent handlers should still run.") } try { @@ -902,17 +912,20 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { completer1.putFinal(5) // put a higher value - try completer1.putFinal(6) catch { + try completer1.putFinal(6) + catch { case _: IllegalStateException => /* ignore */ } // put a lower value - try completer1.putFinal(4) catch { + try completer1.putFinal(4) + catch { case _: IllegalStateException => /* ignore */ } // put the same value - try completer1.putFinal(5) catch { + try completer1.putFinal(5) + catch { case _: IllegalStateException => /* ignore */ } @@ -965,7 +978,7 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { } override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = { - cells.map(cell ⇒ (cell, cell.getResult())) + cells.map(cell => (cell, cell.getResult())) } override def toString = "ReactivePropertyStoreKey" @@ -1072,11 +1085,11 @@ abstract class BaseSuite extends FunSuite with CompleterFactory { } override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = { - cells.map(cell ⇒ (cell, 43)) + cells.map(cell => (cell, 43)) } } } class ConcurrentBaseSuite extends BaseSuite with ConcurrentCompleterFactory -class SequentialBaseSuite extends BaseSuite with SequentialCompleterFactory \ No newline at end of file +class SequentialBaseSuite extends BaseSuite with SequentialCompleterFactory diff --git a/core/src/test/scala/com/phaller/rasync/test/ExceptionSuite.scala b/core/src/test/scala/com/phaller/rasync/test/ExceptionSuite.scala index c15356a..dd09676 100644 --- a/core/src/test/scala/com/phaller/rasync/test/ExceptionSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/ExceptionSuite.scala @@ -25,7 +25,8 @@ class ExceptionSuite extends FunSuite { * behave correclty wrt. exceptions in dependencies and keys. */ - implicit val naturalNumberUpdater: Updater[Int] = Updater.latticeToUpdater(new NaturalNumberLattice) + implicit val naturalNumberUpdater: Updater[Int] = + Updater.latticeToUpdater(new NaturalNumberLattice) implicit def strToIntKey(s: String): NaturalNumberKey.type = NaturalNumberKey test("exception in init") { @@ -159,7 +160,7 @@ class ExceptionSuite extends FunSuite { pool.onQuiescenceShutdown() // check for exceptions in all cells of the cycle - for (c ← List(c0, c1, c2, c3, c4)) + for (c <- List(c0, c1, c2, c3, c4)) c.cell.getTry() match { case Success(_) => assert(false) case Failure(e) => assert(e.getMessage == "foo") @@ -278,7 +279,10 @@ class ExceptionSuite extends FunSuite { // the respective cell. val latch1 = new CountDownLatch(1) val latch2 = new CountDownLatch(1) - implicit val pool: HandlerPool[Int, Null] = new HandlerPool[Int, Null](NaturalNumberKey, unhandledExceptionHandler = _ => latch1.countDown()) + implicit val pool: HandlerPool[Int, Null] = new HandlerPool[Int, Null]( + NaturalNumberKey, + unhandledExceptionHandler = _ => latch1.countDown() + ) val c0 = CellCompleter() val cell = pool.mkCell(c => { // build up dependency, throw error, if c0's value changes diff --git a/core/src/test/scala/com/phaller/rasync/test/InternalBaseSuite.scala b/core/src/test/scala/com/phaller/rasync/test/InternalBaseSuite.scala index 965c91a..8235f81 100644 --- a/core/src/test/scala/com/phaller/rasync/test/InternalBaseSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/InternalBaseSuite.scala @@ -16,7 +16,9 @@ class InternalBaseSuite extends FunSuite { def if10thenFinal20(updates: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = ifXthenFinalY(10, 20)(updates) - def ifXthenFinalY(x: Int, y: Int)(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = { + def ifXthenFinalY(x: Int, y: Int)( + upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])] + ): Outcome[Int] = { val c = upd.head._2 if (c.get.value == x) FinalOutcome(y) else NoOutcome } diff --git a/core/src/test/scala/com/phaller/rasync/test/KeyResolutionSuite.scala b/core/src/test/scala/com/phaller/rasync/test/KeyResolutionSuite.scala index 0be7f2c..ff6bf11 100644 --- a/core/src/test/scala/com/phaller/rasync/test/KeyResolutionSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/KeyResolutionSuite.scala @@ -12,14 +12,10 @@ import scala.concurrent.Await import scala.concurrent.duration._ import scala.util.{ Failure, Success, Try } -/** - * Tests where cylces or independent cells - * need to be resolved via a Key. - * This tests contains cycles that only constist - * of a single type of Cells and do not mix - * SequentialCells and ConcurrentCells. - * For the mixedcase, see MixedKeyResolutionsuite - */ +/** Tests where cylces or independent cells need to be resolved via a Key. This tests contains + * cycles that only constist of a single type of Cells and do not mix SequentialCells and + * ConcurrentCells. For the mixedcase, see MixedKeyResolutionsuite + */ abstract class KeyResolutionSuite extends FunSuite with CompleterFactory { def forwardAsNext(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = { val c = upd.head._2 @@ -79,9 +75,11 @@ abstract class KeyResolutionSuite extends FunSuite with CompleterFactory { val RESOLVEDINCYCLE = 5 val RESOLVEDASINDPENDENT = 10 - override def resolve(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = cells.map((_, RESOLVEDINCYCLE)) + override def resolve(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = + cells.map((_, RESOLVEDINCYCLE)) - override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = cells.map((_, RESOLVEDASINDPENDENT)) + override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = + cells.map((_, RESOLVEDASINDPENDENT)) } implicit val pool = HandlerPool(ConstantKey) @@ -102,15 +100,16 @@ abstract class KeyResolutionSuite extends FunSuite with CompleterFactory { completer4.putNext(-1) // create a cSCC, assert that none of the callbacks get called again. - def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = upd.head._2.get match { - case FinalOutcome(_) => - NoOutcome - case NextOutcome(-1) => - NoOutcome - case _ => - assert(false) - NextOutcome(-2) - } + def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = + upd.head._2.get match { + case FinalOutcome(_) => + NoOutcome + case NextOutcome(-1) => + NoOutcome + case _ => + assert(false) + NextOutcome(-2) + } cell1.when(cell2)(c) cell1.when(cell3)(c) @@ -158,15 +157,16 @@ abstract class KeyResolutionSuite extends FunSuite with CompleterFactory { completer4.putNext(-1) // create a cSCC, assert that none of the callbacks get called again. - def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = upd.head._2.get match { - case FinalOutcome(_) => - NoOutcome - case NextOutcome(-1) => - NoOutcome - case _ => - assert(false) - NextOutcome(-2) - } + def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = + upd.head._2.get match { + case FinalOutcome(_) => + NoOutcome + case NextOutcome(-1) => + NoOutcome + case _ => + assert(false) + NextOutcome(-2) + } cell1.when(cell2)(c) cell1.when(cell3)(c) @@ -231,12 +231,15 @@ abstract class KeyResolutionSuite extends FunSuite with CompleterFactory { case object ShouldNotHappen extends Value implicit object ValueUpdater extends Updater[Value] { - override def update(v1: Value, v2: Value): Value = if (v1 == Bottom) v2 else v1 // TODO or throw? + override def update(v1: Value, v2: Value): Value = + if (v1 == Bottom) v2 else v1 // TODO or throw? override val bottom: Value = Bottom } object TheKey extends DefaultKey[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, OK)) } } @@ -276,10 +279,14 @@ abstract class KeyResolutionSuite extends FunSuite with CompleterFactory { } object TheKey extends Key[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Resolved)) } - override def fallback(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def fallback( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Fallback)) } } @@ -323,10 +330,14 @@ abstract class KeyResolutionSuite extends FunSuite with CompleterFactory { } object TheKey extends Key[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Resolved)) } - override def fallback(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def fallback( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { Seq() } } diff --git a/core/src/test/scala/com/phaller/rasync/test/LatticeSuite.scala b/core/src/test/scala/com/phaller/rasync/test/LatticeSuite.scala index 74e1219..af56b54 100644 --- a/core/src/test/scala/com/phaller/rasync/test/LatticeSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/LatticeSuite.scala @@ -143,7 +143,7 @@ class LatticeSuite extends FunSuite { assert(false) } catch { case lve: NotMonotonicException[_] => assert(true) - case e: Exception => assert(false) + case e: Exception => assert(false) } try { @@ -151,7 +151,7 @@ class LatticeSuite extends FunSuite { assert(false) } catch { case lve: NotMonotonicException[_] => assert(true) - case e: Exception => assert(false) + case e: Exception => assert(false) } } } diff --git a/core/src/test/scala/com/phaller/rasync/test/LazySuite.scala b/core/src/test/scala/com/phaller/rasync/test/LazySuite.scala index e88a5fe..fefa847 100644 --- a/core/src/test/scala/com/phaller/rasync/test/LazySuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/LazySuite.scala @@ -307,4 +307,3 @@ class LazySuite extends FunSuite { pool.shutdown() } } - diff --git a/core/src/test/scala/com/phaller/rasync/test/MixedKeyResolutionSuite.scala b/core/src/test/scala/com/phaller/rasync/test/MixedKeyResolutionSuite.scala index 758357b..9c5e54f 100644 --- a/core/src/test/scala/com/phaller/rasync/test/MixedKeyResolutionSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/MixedKeyResolutionSuite.scala @@ -12,16 +12,14 @@ import scala.concurrent.Await import scala.concurrent.duration._ import scala.util.{ Failure, Success, Try } -/** - * Tests where cylces or independent cells - * need to be resolved via a Key. - * This tests contains cycles that only constist - * of a single type of Cells and do not mix - * SequentialCells and ConcurrentCells. - * For the mixedcase, see MixedKeyResolutionsuite - */ +/** Tests where cylces or independent cells need to be resolved via a Key. This tests contains + * cycles that only constist of a single type of Cells and do not mix SequentialCells and + * ConcurrentCells. For the mixedcase, see MixedKeyResolutionsuite + */ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { - def forwardAsNext[E >: Null](upd: Iterable[(Cell[Int, E], Try[ValueOutcome[Int]])]): Outcome[Int] = { + def forwardAsNext[E >: Null]( + upd: Iterable[(Cell[Int, E], Try[ValueOutcome[Int]])] + ): Outcome[Int] = { val c = upd.head._2 NextOutcome(c.get.value) } @@ -67,9 +65,11 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { val RESOLVEDINCYCLE = 5 val RESOLVEDASINDPENDENT = 10 - override def resolve(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = cells.map((_, RESOLVEDINCYCLE)) + override def resolve(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = + cells.map((_, RESOLVEDINCYCLE)) - override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = cells.map((_, RESOLVEDASINDPENDENT)) + override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = + cells.map((_, RESOLVEDASINDPENDENT)) } implicit val pool = new HandlerPool[Int, Null](ConstantKey) @@ -90,15 +90,16 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { completer4.putNext(-1) // create a cSCC, assert that none of the callbacks get called again. - def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = upd.head._2.get match { - case FinalOutcome(_) => - NoOutcome - case NextOutcome(-1) => - NoOutcome - case _ => - assert(false) - NextOutcome(-2) - } + def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = + upd.head._2.get match { + case FinalOutcome(_) => + NoOutcome + case NextOutcome(-1) => + NoOutcome + case _ => + assert(false) + NextOutcome(-2) + } cell1.when(cell2)(c) cell1.when(cell3)(c) @@ -132,9 +133,11 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { val RESOLVEDINCYCLE = 5 val RESOLVEDASINDPENDENT = 10 - override def resolve(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = cells.map((_, RESOLVEDINCYCLE)) + override def resolve(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = + cells.map((_, RESOLVEDINCYCLE)) - override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = cells.map((_, RESOLVEDASINDPENDENT)) + override def fallback(cells: Iterable[Cell[Int, Null]]): Iterable[(Cell[Int, Null], Int)] = + cells.map((_, RESOLVEDASINDPENDENT)) } implicit val pool = new HandlerPool[Int, Null](ConstantKey) @@ -155,15 +158,16 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { completer4.putNext(-1) // create a cSCC, assert that none of the callbacks get called again. - def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = upd.head._2.get match { - case FinalOutcome(_) => - NoOutcome - case NextOutcome(-1) => - NoOutcome - case _ => - assert(false) - NextOutcome(-2) - } + def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = + upd.head._2.get match { + case FinalOutcome(_) => + NoOutcome + case NextOutcome(-1) => + NoOutcome + case _ => + assert(false) + NextOutcome(-2) + } cell1.when(cell2)(c) cell1.when(cell3)(c) @@ -211,15 +215,16 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { completer4.putNext(-1) // create a cSCC, assert that none of the callbacks get called again. - def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = upd.head._2.get match { - case FinalOutcome(_) => - NoOutcome - case NextOutcome(-1) => - NoOutcome - case _ => - assert(false) - NextOutcome(-2) - } + def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = + upd.head._2.get match { + case FinalOutcome(_) => + NoOutcome + case NextOutcome(-1) => + NoOutcome + case _ => + assert(false) + NextOutcome(-2) + } cell1.when(cell2)(c) cell1.when(cell3)(c) @@ -267,15 +272,16 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { completer4.putNext(-1) // create a cSCC, assert that none of the callbacks get called again. - def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = upd.head._2.get match { - case FinalOutcome(_) => - NoOutcome - case NextOutcome(-1) => - NoOutcome - case _ => - assert(false) - NextOutcome(-2) - } + def c(upd: Iterable[(Cell[Int, Null], Try[ValueOutcome[Int]])]): Outcome[Int] = + upd.head._2.get match { + case FinalOutcome(_) => + NoOutcome + case NextOutcome(-1) => + NoOutcome + case _ => + assert(false) + NextOutcome(-2) + } cell1.when(cell2)(c) cell1.when(cell3)(c) @@ -371,12 +377,15 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { case object ShouldNotHappen extends Value implicit object ValueUpdater extends Updater[Value] { - override def update(v1: Value, v2: Value): Value = if (v1 == Bottom) v2 else v1 // TODO or throw? + override def update(v1: Value, v2: Value): Value = + if (v1 == Bottom) v2 else v1 // TODO or throw? override val bottom: Value = Bottom } object TheKey extends DefaultKey[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, OK)) } } @@ -409,12 +418,15 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { case object ShouldNotHappen extends Value implicit object ValueUpdater extends Updater[Value] { - override def update(v1: Value, v2: Value): Value = if (v1 == Bottom) v2 else v1 // TODO or throw? + override def update(v1: Value, v2: Value): Value = + if (v1 == Bottom) v2 else v1 // TODO or throw? override val bottom: Value = Bottom } object TheKey extends DefaultKey[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, OK)) } } @@ -454,10 +466,14 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { } object TheKey extends DefaultKey[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Resolved)) } - override def fallback(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def fallback( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Fallback)) } } @@ -501,10 +517,14 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { } object TheKey extends DefaultKey[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Resolved)) } - override def fallback(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def fallback( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Fallback)) } } @@ -548,10 +568,14 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { } object TheKey extends DefaultKey[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Resolved)) } - override def fallback(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def fallback( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { Seq() } } @@ -592,10 +616,14 @@ class MixedKeyResolutionSuite extends FunSuite with MixedCompleterFactory { } object TheKey extends Key[Value, Null] { - override def resolve(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def resolve( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { cells.map(cell => (cell, Resolved)) } - override def fallback(cells: Iterable[Cell[Value, Null]]): Iterable[(Cell[Value, Null], Value)] = { + override def fallback( + cells: Iterable[Cell[Value, Null]] + ): Iterable[(Cell[Value, Null], Value)] = { Seq() } } diff --git a/core/src/test/scala/com/phaller/rasync/test/PsSuite.scala b/core/src/test/scala/com/phaller/rasync/test/PsSuite.scala index fa3db3b..2424eac 100644 --- a/core/src/test/scala/com/phaller/rasync/test/PsSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/PsSuite.scala @@ -82,7 +82,7 @@ // } // // override def fallback[K <: Key[Int]](cells: Iterable[Cell[K, Int]]): Iterable[(Cell[K, Int], Int)] = { -// cells.map(cell ⇒ (cell, cell.getResult())) +// cells.map(cell => (cell, cell.getResult())) // } // // override def toString = "ReactivePropertyStoreKey" diff --git a/core/src/test/scala/com/phaller/rasync/test/completerFactory.scala b/core/src/test/scala/com/phaller/rasync/test/completerFactory.scala index bfa4b85..57817eb 100644 --- a/core/src/test/scala/com/phaller/rasync/test/completerFactory.scala +++ b/core/src/test/scala/com/phaller/rasync/test/completerFactory.scala @@ -5,22 +5,51 @@ import com.phaller.rasync.lattice.Updater import com.phaller.rasync.pool.HandlerPool trait CompleterFactory { - def mkCompleter[V, E >: Null](implicit updater: Updater[V], pool: HandlerPool[V, E], e: E = null): CellCompleter[V, E] - def mkCompleter[V](implicit updater: Updater[V], pool: HandlerPool[V, Null]): CellCompleter[V, Null] = mkCompleter[V, Null] + def mkCompleter[V, E >: Null](implicit + updater: Updater[V], + pool: HandlerPool[V, E], + e: E = null + ): CellCompleter[V, E] + def mkCompleter[V](implicit + updater: Updater[V], + pool: HandlerPool[V, Null] + ): CellCompleter[V, Null] = mkCompleter[V, Null] } trait ConcurrentCompleterFactory extends CompleterFactory { - override def mkCompleter[V, E >: Null](implicit updater: Updater[V], pool: HandlerPool[V, E], e: E = null): CellCompleter[V, E] = CellCompleter(entity = e)(updater, pool) + override def mkCompleter[V, E >: Null](implicit + updater: Updater[V], + pool: HandlerPool[V, E], + e: E = null + ): CellCompleter[V, E] = CellCompleter(entity = e)(updater, pool) } trait SequentialCompleterFactory extends CompleterFactory { - override def mkCompleter[V, E >: Null](implicit updater: Updater[V], pool: HandlerPool[V, E], e: E = null): CellCompleter[V, E] = CellCompleter(entity = e, sequential = true)(updater, pool) + override def mkCompleter[V, E >: Null](implicit + updater: Updater[V], + pool: HandlerPool[V, E], + e: E = null + ): CellCompleter[V, E] = CellCompleter(entity = e, sequential = true)(updater, pool) } trait MixedCompleterFactory { - def mkSeqCompleter[V, E >: Null](implicit updater: Updater[V], pool: HandlerPool[V, E], e: E = null): CellCompleter[V, E] = CellCompleter[V, E](sequential = true, entity = e)(updater, pool) - def mkConCompleter[V, E >: Null](implicit updater: Updater[V], pool: HandlerPool[V, E], e: E = null): CellCompleter[V, E] = CellCompleter[V, E](entity = e)(updater, pool) + def mkSeqCompleter[V, E >: Null](implicit + updater: Updater[V], + pool: HandlerPool[V, E], + e: E = null + ): CellCompleter[V, E] = CellCompleter[V, E](sequential = true, entity = e)(updater, pool) + def mkConCompleter[V, E >: Null](implicit + updater: Updater[V], + pool: HandlerPool[V, E], + e: E = null + ): CellCompleter[V, E] = CellCompleter[V, E](entity = e)(updater, pool) - def mkSeqCompleter[V](implicit updater: Updater[V], pool: HandlerPool[V, Null]): CellCompleter[V, Null] = mkSeqCompleter[V, Null] - def mkConCompleter[V](implicit updater: Updater[V], pool: HandlerPool[V, Null]): CellCompleter[V, Null] = mkConCompleter[V, Null] -} \ No newline at end of file + def mkSeqCompleter[V](implicit + updater: Updater[V], + pool: HandlerPool[V, Null] + ): CellCompleter[V, Null] = mkSeqCompleter[V, Null] + def mkConCompleter[V](implicit + updater: Updater[V], + pool: HandlerPool[V, Null] + ): CellCompleter[V, Null] = mkConCompleter[V, Null] +} diff --git a/core/src/test/scala/com/phaller/rasync/test/immutability/ImmutabilityDemo.scala b/core/src/test/scala/com/phaller/rasync/test/immutability/ImmutabilityDemo.scala index 22cfebe..3cf17c4 100644 --- a/core/src/test/scala/com/phaller/rasync/test/immutability/ImmutabilityDemo.scala +++ b/core/src/test/scala/com/phaller/rasync/test/immutability/ImmutabilityDemo.scala @@ -2,7 +2,7 @@ package com.phaller.rasync.test.immutability; // The following mutabilities are defined w.r.t. the case that the class hierarchy is closed. -abstract class Root( final val l: Int) // O: Immutable // T: Mutable +abstract class Root(final val l: Int) // O: Immutable // T: Mutable class SRootI extends Root(1) // O: Immutable // T: Conditionally Immutable @@ -10,9 +10,11 @@ class SRootM(var j: Int) extends Root(1) // O: Mutable // T: Mutable class SSRootMN extends SRootM(-1) // O: Mutable // T: Mutable -class SSRootICM( final val o: SRootM) extends SRootI // O: Conditionally Immutable // T: Conditionally Immutable +class SSRootICM(final val o: SRootM) + extends SRootI // O: Conditionally Immutable // T: Conditionally Immutable -class SRootI_EF extends Root(1) { // [If we have an analysis for effectively final fields…] // O: Immutable // T: Immutable +class SRootI_EF + extends Root(1) { // [If we have an analysis for effectively final fields…] // O: Immutable // T: Immutable // otherwise: // // O: Mutable // T: Mutable // The scala compiler does not generate a setter for the following private field. @@ -57,4 +59,4 @@ RESULTS WHEN USING LIBRAY WITH CLOSED PACKAGES ASSUMPTION [info] immutability.SRootM => MutableObjectByAnalysis => MutableType [info] immutability.SSRootI_EF_I => MutableObjectByAnalysis => MutableType [info] immutability.SSRootMN => MutableObjectByAnalysis => MutableType -*/ + */ diff --git a/core/src/test/scala/com/phaller/rasync/test/lattice/Immutability.scala b/core/src/test/scala/com/phaller/rasync/test/lattice/Immutability.scala index b89a0f8..a969de7 100644 --- a/core/src/test/scala/com/phaller/rasync/test/lattice/Immutability.scala +++ b/core/src/test/scala/com/phaller/rasync/test/lattice/Immutability.scala @@ -7,14 +7,18 @@ import com.phaller.rasync.lattice.{ Key, Lattice } object ImmutabilityKey extends Key[Immutability, Null] { - def resolve(cells: Iterable[Cell[Immutability, Null]]): Iterable[(Cell[Immutability, Null], Immutability)] = { + def resolve( + cells: Iterable[Cell[Immutability, Null]] + ): Iterable[(Cell[Immutability, Null], Immutability)] = { val conditionallyImmutableCells = cells.filter(_.getResult() == ConditionallyImmutable) if (conditionallyImmutableCells.nonEmpty) cells.map(cell => (cell, ConditionallyImmutable)) else cells.map(cell => (cell, Immutable)) } - def fallback(cells: Iterable[Cell[Immutability, Null]]): Iterable[(Cell[Immutability, Null], Immutability)] = { + def fallback( + cells: Iterable[Cell[Immutability, Null]] + ): Iterable[(Cell[Immutability, Null], Immutability)] = { cells.map(cell => (cell, Immutable)) } @@ -36,7 +40,7 @@ object Immutability { override def lteq(lhs: Immutability, rhs: Immutability): Boolean = { lhs == rhs || lhs == Immutable || - (lhs == ConditionallyImmutable && rhs != Immutable) + (lhs == ConditionallyImmutable && rhs != Immutable) } override val bottom: Immutability = Immutable diff --git a/core/src/test/scala/com/phaller/rasync/test/lattice/IntUpdater.scala b/core/src/test/scala/com/phaller/rasync/test/lattice/IntUpdater.scala index 02741a2..9f2b118 100644 --- a/core/src/test/scala/com/phaller/rasync/test/lattice/IntUpdater.scala +++ b/core/src/test/scala/com/phaller/rasync/test/lattice/IntUpdater.scala @@ -29,4 +29,3 @@ class IntUpdater extends Updater[Int] { override val bottom: Int = 0 } - diff --git a/core/src/test/scala/com/phaller/rasync/test/opal/ImmutabilityAnalysis.scala b/core/src/test/scala/com/phaller/rasync/test/opal/ImmutabilityAnalysis.scala index bff3c6a..0cfdb43 100644 --- a/core/src/test/scala/com/phaller/rasync/test/opal/ImmutabilityAnalysis.scala +++ b/core/src/test/scala/com/phaller/rasync/test/opal/ImmutabilityAnalysis.scala @@ -22,7 +22,7 @@ // override def doAnalyze( // project: Project[URL], // parameters: Seq[String] = List.empty, -// isInterrupted: () ⇒ Boolean): BasicReport = { +// isInterrupted: () => Boolean): BasicReport = { // // Run ClassExtensibilityAnalysis // val projectStore = project.get(PropertyStoreKey) // val manager = project.get(FPCFAnalysesManagerKey) @@ -70,10 +70,10 @@ // // All classes that do not have complete superclass information are mutable // // due to the lack of knowledge. // val typesForWhichItMayBePossibleToComputeTheMutability = allSubtypes(ObjectType.Object, reflexive = true) -// val unexpectedRootTypes = rootTypes.filter(rt ⇒ (rt ne ObjectType.Object) && !isInterface(rt).isNo) -// unexpectedRootTypes.map(rt ⇒ allSubtypes(rt, reflexive = true)).flatten.view. -// filter(ot ⇒ !typesForWhichItMayBePossibleToComputeTheMutability.contains(ot)). -// foreach(ot ⇒ project.classFile(ot) foreach { cf ⇒ +// val unexpectedRootTypes = rootTypes.filter(rt => (rt ne ObjectType.Object) && !isInterface(rt).isNo) +// unexpectedRootTypes.map(rt => allSubtypes(rt, reflexive = true)).flatten.view. +// filter(ot => !typesForWhichItMayBePossibleToComputeTheMutability.contains(ot)). +// foreach(ot => project.classFile(ot) foreach { cf => // classFileToObjectTypeCellCompleter(cf)._1.putFinal(Mutable) // }) // @@ -171,10 +171,10 @@ // // All classes that do not have complete superclass information are mutable // // due to the lack of knowledge. // val typesForWhichItMayBePossibleToComputeTheMutability = allSubtypes(ObjectType.Object, reflexive = true) -// val unexpectedRootTypes = rootTypes.filter(rt ⇒ (rt ne ObjectType.Object) && !isInterface(rt).isNo) -// unexpectedRootTypes.map(rt ⇒ allSubtypes(rt, reflexive = true)).flatten.view. -// filter(ot ⇒ !typesForWhichItMayBePossibleToComputeTheMutability.contains(ot)). -// foreach(ot ⇒ project.classFile(ot) foreach { cf ⇒ +// val unexpectedRootTypes = rootTypes.filter(rt => (rt ne ObjectType.Object) && !isInterface(rt).isNo) +// unexpectedRootTypes.map(rt => allSubtypes(rt, reflexive = true)).flatten.view. +// filter(ot => !typesForWhichItMayBePossibleToComputeTheMutability.contains(ot)). +// foreach(ot => project.classFile(ot) foreach { cf => // classFileToObjectTypeCellCompleter(cf)._1.putFinal(Mutable) // }) // @@ -317,13 +317,13 @@ // case ConditionallyImmutable => NextOutcome(ConditionallyImmutable) // }) // } else { -// val unavailableSubtype = directSubtypes.find(t ⇒ project.classFile(t).isEmpty) +// val unavailableSubtype = directSubtypes.find(t => project.classFile(t).isEmpty) // if (unavailableSubtype.isDefined) // cellCompleter.putFinal(Mutable) // // if (!cellCompleter.cell.isComplete) { // // Check subclasses to determine TypeImmutability -// val directSubclasses = directSubtypes map { subtype ⇒ project.classFile(subtype).get } +// val directSubclasses = directSubtypes map { subtype => project.classFile(subtype).get } // directSubclasses foreach { subclass => // cellCompleter.cell.whenNext( // classFileToObjectTypeCellCompleter(subclass)._2.cell, diff --git a/core/src/test/scala/com/phaller/rasync/test/opal/OPALSuite.scala b/core/src/test/scala/com/phaller/rasync/test/opal/OPALSuite.scala index c388ee7..2d0c650 100644 --- a/core/src/test/scala/com/phaller/rasync/test/opal/OPALSuite.scala +++ b/core/src/test/scala/com/phaller/rasync/test/opal/OPALSuite.scala @@ -32,7 +32,8 @@ class OPALSuite extends FunSuite { "pureness.Demo{ static int scc0(int) }", "pureness.Demo{ static int scc1(int) }", "pureness.Demo{ static int scc2(int) }", - "pureness.Demo{ static int scc3(int) }") + "pureness.Demo{ static int scc3(int) }" + ) val finalRes = pureMethods.filter(!report.contains(_)) @@ -60,7 +61,8 @@ class OPALSuite extends FunSuite { "static int cpureCalleeCallee1(int)", "static int cpureCalleeCallee2(int)", "static int cpureCalleeCalleeCallee(int)", - "static int cpureCalleeCalleeCalleeCallee(int)") + "static int cpureCalleeCalleeCalleeCallee(int)" + ) val finalRes = impureMethods.filter(report.contains(_)) diff --git a/core/src/test/scala/com/phaller/rasync/test/opal/PurityAnalysis.scala b/core/src/test/scala/com/phaller/rasync/test/opal/PurityAnalysis.scala index a1b0753..cac749d 100644 --- a/core/src/test/scala/com/phaller/rasync/test/opal/PurityAnalysis.scala +++ b/core/src/test/scala/com/phaller/rasync/test/opal/PurityAnalysis.scala @@ -68,31 +68,36 @@ import org.opalj.br.DeclaredMethod // A strategy tailored to PurityAnalysis object PurityStrategy extends SchedulingStrategy[Purity, Null] { - override def calcPriority(dependentCell: Cell[Purity, Null], other: Cell[Purity, Null], value: Try[ValueOutcome[Purity]]): Int = value match { + override def calcPriority( + dependentCell: Cell[Purity, Null], + other: Cell[Purity, Null], + value: Try[ValueOutcome[Purity]] + ): Int = value match { case scala.util.Success(FinalOutcome(Impure)) => -1 - case _ => 1 + case _ => 1 } - override def calcPriority(dependentCell: Cell[Purity, Null], value: Try[Purity]): Int = value match { - case scala.util.Success(Pure) => 0 - case _ => -1 - } + override def calcPriority(dependentCell: Cell[Purity, Null], value: Try[Purity]): Int = + value match { + case scala.util.Success(Pure) => 0 + case _ => -1 + } } object PurityAnalysis extends ProjectAnalysisApplication { override def main(args: Array[String]): Unit = { - val lib = Project(new java.io.File(args(args.length - 1))) //JRELibraryFolder.getAbsolutePath)) + val lib = Project(new java.io.File(args(args.length - 1))) // JRELibraryFolder.getAbsolutePath)) println("Heap size: " + Runtime.getRuntime().maxMemory()) schedulingStrategy = new DefaultScheduling[Purity, Null] PerformanceEvaluation.time { val report = PurityAnalysis.doAnalyze(lib.recreate(), List.empty, () => false) - } { t ⇒ println(s"DefaultScheduling(Warmup),${t.timeSpan}") } + } { t => println(s"DefaultScheduling(Warmup),${t.timeSpan}") } for { - scheduling ← List( + scheduling <- List( new DefaultScheduling[Purity, Null], new SourcesWithManyTargetsFirst[Purity, Null], new SourcesWithManyTargetsLast[Purity, Null], @@ -102,28 +107,31 @@ object PurityAnalysis extends ProjectAnalysisApplication { new TargetsWithManyTargetsLast[Purity, Null], new SourcesWithManySourcesFirst[Purity, Null], new SourcesWithManySourcesLast[Purity, Null], - PurityStrategy) - i ← (0 until 5) + PurityStrategy + ) + i <- (0 until 5) } { val p = lib.recreate() schedulingStrategy = scheduling PerformanceEvaluation.time { val report = PurityAnalysis.doAnalyze(p, List.empty, () => false) - } { t ⇒ println(s"$scheduling,${t.timeSpan}") } - //println(report.toConsoleString.split("\n").slice(0, 2).mkString("\n")) + } { t => println(s"$scheduling,${t.timeSpan}") } + // println(report.toConsoleString.split("\n").slice(0, 2).mkString("\n")) } } - var schedulingStrategy: SchedulingStrategy[Purity, Null] = null + var schedulingStrategy: SchedulingStrategy[Purity, Null] = new DefaultScheduling() override def doAnalyze( - project: Project[URL], - parameters: Seq[String] = List.empty, - isInterrupted: () ⇒ Boolean): BasicReport = { + project: Project[URL], + parameters: Seq[String] = List.empty, + isInterrupted: () => Boolean + ): BasicReport = { val startTime = System.currentTimeMillis // Used for measuring execution time // 1. Initialization of key data structures (one cell(completer) per method) - implicit val pool: HandlerPool[Purity, Null] = new HandlerPool(key = PurityKey, parallelism = 10, schedulingStrategy = schedulingStrategy) + implicit val pool: HandlerPool[Purity, Null] = + new HandlerPool(key = PurityKey, parallelism = 10, schedulingStrategy = schedulingStrategy) var methodToCell = Map.empty[Method, Cell[Purity, Null]] for { classFile <- project.allProjectClassFiles @@ -139,7 +147,7 @@ object PurityAnalysis extends ProjectAnalysisApplication { // 2. trigger analyses for { - classFile <- project.allProjectClassFiles.par + classFile <- project.allProjectClassFiles method <- classFile.methods } { methodToCell(method).trigger() @@ -154,36 +162,41 @@ object PurityAnalysis extends ProjectAnalysisApplication { val analysisTime = endTime - middleTime val combinedTime = endTime - startTime - val pureMethods = methodToCell.filter(_._2.getResult() match { - case Pure => true - case _ => false - }).keys + val pureMethods = methodToCell + .filter(_._2.getResult() match { + case Pure => true + case _ => false + }) + .keys val pureMethodsInfo = pureMethods.map(m => m.toJava).toList.sorted - BasicReport(s"pure methods analysis:\nPURE=${pureMethods.size}\n\n" + pureMethodsInfo.mkString("\n") + - s"\nSETUP TIME: $setupTime" + - s"\nANALYIS TIME: $analysisTime" + - s"\nCOMBINED TIME: $combinedTime") + BasicReport( + s"pure methods analysis:\nPURE=${pureMethods.size}\n\n" + pureMethodsInfo.mkString("\n") + + s"\nSETUP TIME: $setupTime" + + s"\nANALYIS TIME: $analysisTime" + + s"\nCOMBINED TIME: $combinedTime" + ) } - /** - * Determines the purity of the given method. - */ + /** Determines the purity of the given method. + */ def analyze( - project: Project[URL], - methodToCell: Map[Method, Cell[Purity, Null]], - classFile: ClassFile, - method: Method): Outcome[Purity] = { + project: Project[URL], + methodToCell: Map[Method, Cell[Purity, Null]], + classFile: ClassFile, + method: Method + ): Outcome[Purity] = { import project.nonVirtualCall val cell = methodToCell(method) if ( // Due to a lack of knowledge, we classify all native methods or methods that - // belong to a library (and hence lack the body) as impure... - method.body.isEmpty /*HERE: method.isNative || "isLibraryMethod(method)"*/ || + // belong to a library (and hence lack the body) as impure... + method.body.isEmpty /*HERE: method.isNative || "isLibraryMethod(method)"*/ || // for simplicity we are just focusing on methods that do not take objects as parameters - method.parameterTypes.exists(!_.isBaseType)) { + method.parameterTypes.exists(!_.isBaseType) + ) { return FinalOutcome(Impure) } @@ -200,63 +213,61 @@ object PurityAnalysis extends ProjectAnalysisApplication { val instruction = instructions(currentPC) (instruction.opcode: @scala.annotation.switch) match { - case GETSTATIC.opcode ⇒ + case GETSTATIC.opcode => val GETSTATIC(declaringClass, fieldName, fieldType) = instruction import project.resolveFieldReference resolveFieldReference(declaringClass, fieldName, fieldType) match { - case Some(field) if field.isFinal ⇒ NoOutcome + case Some(field) if field.isFinal => NoOutcome /* Nothing to do; constants do not impede purity! */ - // case Some(field) if field.isPrivate /*&& field.isNonFinal*/ ⇒ + // case Some(field) if field.isPrivate /*&& field.isNonFinal*/ => // check if the field is effectively final - case _ ⇒ + case _ => return FinalOutcome(Impure); } - case INVOKESPECIAL.opcode | INVOKESTATIC.opcode ⇒ instruction match { - - case MethodInvocationInstruction(`declaringClassType`, _, `methodName`, `methodDescriptor`) ⇒ - // We have a self-recursive call; such calls do not influence - // the computation of the method's purity and are ignored. - // Let's continue with the evaluation of the next instruction. - - case mii: NonVirtualMethodInvocationInstruction ⇒ - - nonVirtualCall(method.classFile.thisType, mii) match { - - case Success(callee) ⇒ - /* Recall that self-recursive calls are handled earlier! */ - dependencies.add(callee) - - case _ /* Empty or Failure */ ⇒ - - // We know nothing about the target method (it is not - // found in the scope of the current project). - return FinalOutcome(Impure) - } - - } - - case NEW.opcode | - GETFIELD.opcode | - PUTFIELD.opcode | PUTSTATIC.opcode | - NEWARRAY.opcode | MULTIANEWARRAY.opcode | ANEWARRAY.opcode | - AALOAD.opcode | AASTORE.opcode | - BALOAD.opcode | BASTORE.opcode | - CALOAD.opcode | CASTORE.opcode | - SALOAD.opcode | SASTORE.opcode | - IALOAD.opcode | IASTORE.opcode | - LALOAD.opcode | LASTORE.opcode | - DALOAD.opcode | DASTORE.opcode | - FALOAD.opcode | FASTORE.opcode | - ARRAYLENGTH.opcode | - MONITORENTER.opcode | MONITOREXIT.opcode | - INVOKEDYNAMIC.opcode | INVOKEVIRTUAL.opcode | INVOKEINTERFACE.opcode ⇒ + case INVOKESPECIAL.opcode | INVOKESTATIC.opcode => + instruction match { + + case MethodInvocationInstruction( + `declaringClassType`, + _, + `methodName`, + `methodDescriptor` + ) => + // We have a self-recursive call; such calls do not influence + // the computation of the method's purity and are ignored. + // Let's continue with the evaluation of the next instruction. + + case mii: NonVirtualMethodInvocationInstruction => + + nonVirtualCall(method.classFile.thisType, mii) match { + + case Success(callee) => + /* Recall that self-recursive calls are handled earlier! */ + dependencies.add(callee) + + case _ /* Empty or Failure */ => + + // We know nothing about the target method (it is not + // found in the scope of the current project). + return FinalOutcome(Impure) + } + + } + + case NEW.opcode | GETFIELD.opcode | PUTFIELD.opcode | PUTSTATIC.opcode | NEWARRAY.opcode | + MULTIANEWARRAY.opcode | ANEWARRAY.opcode | AALOAD.opcode | AASTORE.opcode | + BALOAD.opcode | BASTORE.opcode | CALOAD.opcode | CASTORE.opcode | SALOAD.opcode | + SASTORE.opcode | IALOAD.opcode | IASTORE.opcode | LALOAD.opcode | LASTORE.opcode | + DALOAD.opcode | DASTORE.opcode | FALOAD.opcode | FASTORE.opcode | ARRAYLENGTH.opcode | + MONITORENTER.opcode | MONITOREXIT.opcode | INVOKEDYNAMIC.opcode | INVOKEVIRTUAL.opcode | + INVOKEINTERFACE.opcode => return FinalOutcome(Impure) - case _ ⇒ + case _ => /* All other instructions (IFs, Load/Stores, Arith., etc.) are pure. */ } currentPC = body.pcOfNextInstruction(currentPC) @@ -275,10 +286,12 @@ object PurityAnalysis extends ProjectAnalysisApplication { // If any dependee is Impure, the dependent Cell is impure. // Otherwise, we do not know anything new. // Exception will be rethrown. - if (v.collectFirst({ - case (_, scala.util.Success(FinalOutcome(Impure))) => true - case (_, scala.util.Failure(_)) => true - }).isDefined) + if ( + v.collectFirst({ + case (_, scala.util.Success(FinalOutcome(Impure))) => true + case (_, scala.util.Failure(_)) => true + }).isDefined + ) FinalOutcome(Impure) else NoOutcome } diff --git a/core/src/test/scala/com/phaller/rasync/test/opal/ifds/AbstractIFDSAnalysis.scala b/core/src/test/scala/com/phaller/rasync/test/opal/ifds/AbstractIFDSAnalysis.scala index a33dace..9b3eee7 100644 --- a/core/src/test/scala/com/phaller/rasync/test/opal/ifds/AbstractIFDSAnalysis.scala +++ b/core/src/test/scala/com/phaller/rasync/test/opal/ifds/AbstractIFDSAnalysis.scala @@ -6,7 +6,7 @@ import java.util.concurrent.ConcurrentHashMap import com.phaller.rasync.lattice.{ Key, Lattice } import com.phaller.rasync.cell._ import com.phaller.rasync.pool.{ HandlerPool, SchedulingStrategy } -import scala.collection.{ Set ⇒ SomeSet } +import scala.collection.{ Set => SomeSet } import org.opalj.br.DeclaredMethod import org.opalj.br.Method @@ -44,123 +44,157 @@ import com.phaller.rasync.test.opal.ifds.AbstractIFDSAnalysis.V import org.opalj.fpcf.FinalEP import org.opalj.br.fpcf.PropertyStoreKey -import org.opalj.br.fpcf.properties.cg.Callees +import org.opalj.tac.fpcf.analyses.cg.TypeIterator +import org.opalj.tac.fpcf.properties.cg.Callees import org.opalj.tac.LazyDetachedTACAIKey import org.opalj.tac.cg.CHACallGraphKey import org.opalj.tac.cg.RTACallGraphKey import org.opalj.tac.Return import org.opalj.tac.ReturnValue +import org.opalj.tac.fpcf.analyses.cg.CHATypeIterator -/** - * The super type of all IFDS facts. - * - */ +/** The super type of all IFDS facts. + */ trait AbstractIFDSFact -/** - * - * The super type of all null facts. - * - */ +/** The super type of all null facts. + */ trait AbstractIFDSNullFact extends AbstractIFDSFact -/** - * A framework for IFDS analyses. - * - * @tparam DataFlowFact The type of flow facts the concrete analysis wants to track - * - * @author Dominik Helm - * @author Jan Kölzer (adaption to Reactive Async) - */ +/** A framework for IFDS analyses. + * + * @tparam DataFlowFact + * The type of flow facts the concrete analysis wants to track + * + * @author + * Dominik Helm + * @author + * Jan Kölzer (adaption to Reactive Async) + */ // The `scheduling` is only for testing. In production, one would create a HandlerPool with the best scheduling for IFDS -abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelism: Int, scheduling: SchedulingStrategy[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)])(implicit project: Project[_]) { +abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact]( + parallelism: Int, + scheduling: SchedulingStrategy[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)] +)(implicit project: Project[_]) { - private val tacProvider: Method ⇒ TACode[TACMethodParameter, DUVar[ValueInformation]] = project.get(LazyDetachedTACAIKey) + private val tacProvider: Method => TACode[TACMethodParameter, DUVar[ValueInformation]] = + project.get(LazyDetachedTACAIKey) val classHierarchy = project.classHierarchy // [p. ackland] "Both resolve and fallback return the empty set for each cell because on quiescence we know that no more propagations will be made and the cell can be completed." object TheKey extends Key[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)] { - override def resolve(cells: Iterable[Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)]]): Iterable[(Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)], IFDSProperty[DataFlowFact])] = { + override def resolve( + cells: Iterable[Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)]] + ): Iterable[ + (Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)], IFDSProperty[DataFlowFact]) + ] = { val p = createProperty(Map.empty) cells.map((_, p)) } - override def fallback(cells: Iterable[Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)]]): Iterable[(Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)], IFDSProperty[DataFlowFact])] = { + override def fallback( + cells: Iterable[Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)]] + ): Iterable[ + (Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)], IFDSProperty[DataFlowFact]) + ] = { val p = createProperty(Map.empty) cells.map((_, p)) } } implicit object TheLattice extends Lattice[IFDSProperty[DataFlowFact]] { - override def join(v1: IFDSProperty[DataFlowFact], v2: IFDSProperty[DataFlowFact]): IFDSProperty[DataFlowFact] = + override def join( + v1: IFDSProperty[DataFlowFact], + v2: IFDSProperty[DataFlowFact] + ): IFDSProperty[DataFlowFact] = createProperty(mergeMaps(v1.flows, v2.flows)) override val bottom: IFDSProperty[DataFlowFact] = createProperty(Map.empty) } - implicit val pool: HandlerPool[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)] = new HandlerPool[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)](key = TheKey, parallelism = parallelism, schedulingStrategy = scheduling) + implicit val pool: HandlerPool[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)] = + new HandlerPool[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)]( + key = TheKey, + parallelism = parallelism, + schedulingStrategy = scheduling + ) // Each cell represents a Method + the flow facts currently known. // The following maps maps (method,fact) to cells - private val mfToCell = TrieMap.empty[(DeclaredMethod, DataFlowFact), Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)]] + private val mfToCell = TrieMap.empty[(DeclaredMethod, DataFlowFact), Cell[IFDSProperty[ + DataFlowFact + ], (DeclaredMethod, DataFlowFact)]] - /** - * Provides the concrete property key (that must be unique for every distinct concrete analysis - * and the lower bound for the IFDSProperty. - */ + /** Provides the concrete property key (that must be unique for every distinct concrete analysis + * and the lower bound for the IFDSProperty. + */ val property: IFDSPropertyMetaInformation[DataFlowFact] /** Creates the concrete IFDSProperty. */ def createProperty(result: Map[Statement, Set[DataFlowFact]]): IFDSProperty[DataFlowFact] - /** - * Computes the DataFlowFacts valid after statement `stmt` on the CFG edge to statement `succ` - * if the DataFlowFacts `in` held before `stmt`. - */ + /** Computes the DataFlowFacts valid after statement `stmt` on the CFG edge to statement `succ` if + * the DataFlowFacts `in` held before `stmt`. + */ def normalFlow(stmt: Statement, succ: Statement, in: Set[DataFlowFact]): Set[DataFlowFact] - /** - * Computes the DataFlowFacts valid on entry to method `callee` when it is called from statement - * `stmt` if the DataFlowFacts `in` held before `stmt`. - */ + /** Computes the DataFlowFacts valid on entry to method `callee` when it is called from statement + * `stmt` if the DataFlowFacts `in` held before `stmt`. + */ def callFlow(stmt: Statement, callee: DeclaredMethod, in: Set[DataFlowFact]): Set[DataFlowFact] - /** - * Computes the DataFlowFacts valid on the CFG edge from statement `stmt` to `succ` if `callee` - * was invoked by `stmt` and DataFlowFacts `in` held before the final statement `exit` of - * `callee`. - */ - def returnFlow(stmt: Statement, callee: DeclaredMethod, exit: Statement, succ: Statement, in: Set[DataFlowFact]): Set[DataFlowFact] - - /** - * Computes the DataFlowFacts valid on the CFG edge from statement `stmt` to `succ` irrespective - * of the call in `stmt` if the DataFlowFacts `in` held before `stmt`. - */ + /** Computes the DataFlowFacts valid on the CFG edge from statement `stmt` to `succ` if `callee` + * was invoked by `stmt` and DataFlowFacts `in` held before the final statement `exit` of + * `callee`. + */ + def returnFlow( + stmt: Statement, + callee: DeclaredMethod, + exit: Statement, + succ: Statement, + in: Set[DataFlowFact] + ): Set[DataFlowFact] + + /** Computes the DataFlowFacts valid on the CFG edge from statement `stmt` to `succ` irrespective + * of the call in `stmt` if the DataFlowFacts `in` held before `stmt`. + */ def callToReturnFlow(stmt: Statement, succ: Statement, in: Set[DataFlowFact]): Set[DataFlowFact] - /** - * Computes the data flow for a summary edge of a native method call. - * - * @param call The statement, which invoked the call. - * @param callee The method, called by `call`. - * @param successor The statement, which will be executed after the call. - * @param in Some facts valid before the `call`. - * @return The facts valid after the call, excluding the call-to-return flow. - */ - def nativeCall(call: Statement, callee: DeclaredMethod, successor: Statement, in: Set[DataFlowFact]): Set[DataFlowFact] + /** Computes the data flow for a summary edge of a native method call. + * + * @param call + * The statement, which invoked the call. + * @param callee + * The method, called by `call`. + * @param successor + * The statement, which will be executed after the call. + * @param in + * Some facts valid before the `call`. + * @return + * The facts valid after the call, excluding the call-to-return flow. + */ + def nativeCall( + call: Statement, + callee: DeclaredMethod, + successor: Statement, + in: Set[DataFlowFact] + ): Set[DataFlowFact] implicit protected[this] val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey) class State( - val declaringClass: ObjectType, - val method: Method, - val source: (DeclaredMethod, DataFlowFact), - val code: Array[Stmt[V]], - val cfg: CFG[Stmt[V], TACStmts[V]], - var pendingIfdsCallSites: Map[(DeclaredMethod, DataFlowFact), Set[(BasicBlock, Int)]], - var pendingIfdsDependees: Map[Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)], Outcome[IFDSProperty[DataFlowFact]]] = Map.empty, - var incomingFacts: Map[BasicBlock, Set[DataFlowFact]] = Map.empty, - var outgoingFacts: Map[BasicBlock, Map[CFGNode, Set[DataFlowFact]]] = Map.empty) + val declaringClass: ObjectType, + val method: Method, + val source: (DeclaredMethod, DataFlowFact), + val code: Array[Stmt[V]], + val cfg: CFG[Stmt[V], TACStmts[V]], + var pendingIfdsCallSites: Map[(DeclaredMethod, DataFlowFact), Set[(BasicBlock, Int)]], + var pendingIfdsDependees: Map[Cell[IFDSProperty[ + DataFlowFact + ], (DeclaredMethod, DataFlowFact)], Outcome[IFDSProperty[DataFlowFact]]] = Map.empty, + var incomingFacts: Map[BasicBlock, Set[DataFlowFact]] = Map.empty, + var outgoingFacts: Map[BasicBlock, Map[CFGNode, Set[DataFlowFact]]] = Map.empty + ) def waitForCompletion(d: Duration): Unit @@ -168,22 +202,28 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis def getResult(e: (DeclaredMethod, DataFlowFact)): IFDSProperty[DataFlowFact] = cell(e).getResult() - /** Map (method, fact) pairs to cells. A new cell is created, if it does not exist yet. See also mf() for the reverse direction. */ - private def cell(source: (DeclaredMethod, DataFlowFact)): Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)] = { + /** Map (method, fact) pairs to cells. A new cell is created, if it does not exist yet. See also + * mf() for the reverse direction. + */ + private def cell( + source: (DeclaredMethod, DataFlowFact) + ): Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)] = { // Can performance be improved if we first check, if mfToCell.isDefinedAt(source) first? - val c = pool.mkSequentialCell(c ⇒ performAnalysis(source), source) - mfToCell.putIfAbsent(source, c) + val c = pool.mkSequentialCell(c => performAnalysis(source), source) + mfToCell + .putIfAbsent(source, c) .getOrElse(c) } - /** Start the computation for a source. Afterwards, wait for completion.*/ + /** Start the computation for a source. Afterwards, wait for completion. */ def forceComputation(source: (DeclaredMethod, DataFlowFact)): Unit = cell(source).trigger() - /** - * Performs IFDS analysis for one specific entity, i.e. one DeclaredMethod/DataFlowFact pair. - */ - def performAnalysis(source: (DeclaredMethod, DataFlowFact)): Outcome[IFDSProperty[DataFlowFact]] = { + /** Performs IFDS analysis for one specific entity, i.e. one DeclaredMethod/DataFlowFact pair. + */ + def performAnalysis( + source: (DeclaredMethod, DataFlowFact) + ): Outcome[IFDSProperty[DataFlowFact]] = { val (declaredMethod, sourceFact) = source // Deal only with single defined methods for now @@ -204,21 +244,23 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis val code = tac.instructions implicit val state: State = - new State(declaringClass, method, source, code, cfg, Map(source → Set.empty)) + new State(declaringClass, method, source, code, cfg, Map(source -> Set.empty)) // Start processing at the start of the cfg with the given source fact val start = cfg.startBlock - state.incomingFacts += start → Set(sourceFact) + state.incomingFacts += start -> Set(sourceFact) process(mutable.Queue((start, Set(sourceFact), None, None, None))) createResult() } - /** - * Processes a queue of BasicBlocks where new DataFlowFacts are available. - */ + /** Processes a queue of BasicBlocks where new DataFlowFacts are available. + */ def process( - initialWorklist: mutable.Queue[(BasicBlock, Set[DataFlowFact], Option[Int], Option[Method], Option[DataFlowFact])])(implicit state: State): Unit = { + initialWorklist: mutable.Queue[ + (BasicBlock, Set[DataFlowFact], Option[Int], Option[Method], Option[DataFlowFact]) + ] + )(implicit state: State): Unit = { val worklist = initialWorklist while (worklist.nonEmpty) { @@ -228,16 +270,22 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis val allOut = mergeMaps(oldOut, nextOut) state.outgoingFacts = state.outgoingFacts.updated(bb, allOut) - for (successor ← bb.successors) { + for (successor <- bb.successors) { if (successor.isExitNode) { // Handle self-dependencies: Propagate new information to self calls val nextOutSuccessors = nextOut.get(successor) if (nextOutSuccessors.isDefined && nextOutSuccessors.get.nonEmpty) { val oldOutSuccessors = oldOut.get(successor) - if (oldOutSuccessors.isEmpty || - nextOutSuccessors.get.exists(nos ⇒ !oldOutSuccessors.get.contains(nos))) { + if ( + oldOutSuccessors.isEmpty || + nextOutSuccessors.get.exists(nos => !oldOutSuccessors.get.contains(nos)) + ) { val source = state.source - reAnalyzeCalls(state.pendingIfdsCallSites(source), source._1.definedMethod, Some(source._2)) + reAnalyzeCalls( + state.pendingIfdsCallSites(source), + source._1.definedMethod, + Some(source._2) + ) } } /*if ((nextOut.getOrElse(successor, Set.empty) -- oldOut.getOrElse(successor, Set.empty)).nonEmpty) { @@ -246,12 +294,12 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis }*/ } else { val succ = (if (successor.isBasicBlock) { - successor - } else { // Skip CatchNodes directly to their handler BasicBlock - /*assert(successor.isCatchNode) + successor + } else { // Skip CatchNodes directly to their handler BasicBlock + /*assert(successor.isCatchNode) assert(successor.successors.size == 1)*/ - successor.successors.head - }).asBasicBlock + successor.successors.head + }).asBasicBlock val nextIn = nextOut.getOrElse(succ, Set.empty) val oldIn = state.incomingFacts.getOrElse(succ, Set.empty) @@ -266,33 +314,41 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis } } - /** - * Gets, for an ExitNode of the CFG, the DataFlowFacts valid on each CFG edge from a - * statement to that ExitNode. - */ + /** Gets, for an ExitNode of the CFG, the DataFlowFacts valid on each CFG edge from a statement to + * that ExitNode. + */ def collectResult(node: CFGNode)(implicit state: State): Map[Statement, Set[DataFlowFact]] = { var result = Map.empty[Statement, Set[DataFlowFact]] - node.predecessors foreach { predecessor ⇒ + node.predecessors foreach { predecessor => if (predecessor.isBasicBlock) { val basicBlock = predecessor.asBasicBlock // FIXME ... replace flatMap...isDefined by something that doesn't create intermediate data-structures if (state.outgoingFacts.get(basicBlock).flatMap(_.get(node)).isDefined) { val lastIndex = basicBlock.endPC - val stmt = Statement(state.method, basicBlock, state.code(lastIndex), lastIndex, state.code, state.cfg) - result += stmt → state.outgoingFacts(basicBlock)(node) + val stmt = Statement( + state.method, + basicBlock, + state.code(lastIndex), + lastIndex, + state.code, + state.cfg + ) + result += stmt -> state.outgoingFacts(basicBlock)(node) } } } result } - /** - * Creates the analysis result from the current state. - */ + /** Creates the analysis result from the current state. + */ def createResult()(implicit state: State): Outcome[IFDSProperty[DataFlowFact]] = { - val propertyValue = createProperty(mergeMaps( - collectResult(state.cfg.normalReturnNode), - collectResult(state.cfg.abnormalReturnNode))) + val propertyValue = createProperty( + mergeMaps( + collectResult(state.cfg.normalReturnNode), + collectResult(state.cfg.abnormalReturnNode) + ) + ) val dependees = state.pendingIfdsDependees.keys @@ -305,36 +361,44 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis } } - def cont(updates: Iterable[(Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)], Try[ValueOutcome[IFDSProperty[DataFlowFact]]])])(implicit state: State): Outcome[IFDSProperty[DataFlowFact]] = { + def cont( + updates: Iterable[ + ( + Cell[IFDSProperty[DataFlowFact], (DeclaredMethod, DataFlowFact)], + Try[ValueOutcome[IFDSProperty[DataFlowFact]]] + ) + ] + )(implicit state: State): Outcome[IFDSProperty[DataFlowFact]] = { handleCallUpdates(updates.map(_._1.entity)) createResult() } def analyzeBasicBlock( - basicBlock: BasicBlock, - in: Set[DataFlowFact], - calleeWithUpdateIndex: Option[Int], - calleeWithUpdate: Option[Method], - calleeWithUpdateFact: Option[DataFlowFact])( - implicit - state: State): Map[CFGNode, Set[DataFlowFact]] = { + basicBlock: BasicBlock, + in: Set[DataFlowFact], + calleeWithUpdateIndex: Option[Int], + calleeWithUpdate: Option[Method], + calleeWithUpdateFact: Option[DataFlowFact] + )(implicit state: State): Map[CFGNode, Set[DataFlowFact]] = { /* - * Collects information about a statement. - * + * Collects information about a statement. + * * @param index The statement's index. - * @return A tuple of the following elements: - * statement: The statement at `index`. - * calees: The methods possibly called at this statement, if it contains a call. - * If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdate` will be returned. - * calleeFact: If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdateFact` will be returned, None otherwise. - */ + * @return A tuple of the following elements: + * statement: The statement at `index`. + * calees: The methods possibly called at this statement, if it contains a call. + * If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdate` will be returned. + * calleeFact: If `index` equals `calleeWithUpdateIndex`, only `calleeWithUpdateFact` will be returned, None otherwise. + */ def collectInformation( - index: Int): (Statement, Option[SomeSet[Method]], Option[DataFlowFact]) = { + index: Int + ): (Statement, Option[SomeSet[Method]], Option[DataFlowFact]) = { val stmt = state.code(index) val statement = Statement(state.method, basicBlock, stmt, index, state.code, state.cfg) val calleesO = - if (calleeWithUpdateIndex.contains(index)) calleeWithUpdate.map(Set(_)) else getCalleesIfCallStatement(basicBlock, index) + if (calleeWithUpdateIndex.contains(index)) calleeWithUpdate.map(Set(_)) + else getCalleesIfCallStatement(basicBlock, index) val calleeFact = if (calleeWithUpdateIndex.contains(index)) calleeWithUpdateFact else None (statement, calleesO, calleeFact) } @@ -347,7 +411,14 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis val (statement, calleesO, calleeFact) = collectInformation(index) flows = if (calleesO.isEmpty) { val successor = - Statement(state.method, basicBlock, state.code(index + 1), index + 1, state.code, state.cfg) + Statement( + state.method, + basicBlock, + state.code(index + 1), + index + 1, + state.code, + state.cfg + ) normalFlow(statement, successor, flows) } else // Inside a basic block, we only have one successor --> Take the head @@ -360,195 +431,232 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis var result: Map[CFGNode, Set[DataFlowFact]] = if (calleesO.isEmpty) { var result: Map[CFGNode, Set[DataFlowFact]] = Map.empty - for (node ← basicBlock.successors) { - result += node → normalFlow(statement, firstStatement(node), flows) + for (node <- basicBlock.successors) { + result += node -> normalFlow(statement, firstStatement(node), flows) } result } else { - handleCall(basicBlock, statement, calleesO.get, flows, callFact).map(entry ⇒ entry._1.node → entry._2) + handleCall(basicBlock, statement, calleesO.get, flows, callFact).map(entry => + entry._1.node -> entry._2 + ) } // Propagate the null fact. - result = result.map(result ⇒ result._1 → (propagateNullFact(in, result._2))) + result = result.map(result => result._1 -> (propagateNullFact(in, result._2))) result } /** Gets the expression from an assingment/expr statement. */ def getExpression(stmt: Stmt[V]): Expr[V] = stmt.astID match { - case Assignment.ASTID ⇒ stmt.asAssignment.expr - case ExprStmt.ASTID ⇒ stmt.asExprStmt.expr - case _ ⇒ throw new UnknownError("Unexpected statement") + case Assignment.ASTID => stmt.asAssignment.expr + case ExprStmt.ASTID => stmt.asExprStmt.expr + case _ => throw new UnknownError("Unexpected statement") } - /** - * Gets the set of all methods possibly called at some statement. - * - * @param basicBlock The basic block containing the statement. - * @param index The statement's index. - * @return All methods possibly called at the statement index or None, if the statement does not contain a call. - */ - def getCalleesIfCallStatement(basicBlock: BasicBlock, index: Int)(implicit state: State): Option[SomeSet[Method]] = { + /** Gets the set of all methods possibly called at some statement. + * + * @param basicBlock + * The basic block containing the statement. + * @param index + * The statement's index. + * @return + * All methods possibly called at the statement index or None, if the statement does not + * contain a call. + */ + def getCalleesIfCallStatement(basicBlock: BasicBlock, index: Int)(implicit + state: State + ): Option[SomeSet[Method]] = { val statement = state.code(index) val pc = statement.pc statement.astID match { - case StaticMethodCall.ASTID | NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID ⇒ Some(getCallees(basicBlock, pc)) - case Assignment.ASTID | ExprStmt.ASTID ⇒ getExpression(statement).astID match { - case StaticFunctionCall.ASTID | NonVirtualFunctionCall.ASTID | VirtualFunctionCall.ASTID ⇒ - Some(getCallees(basicBlock, pc)) - case _ ⇒ None - } - case _ ⇒ None + case StaticMethodCall.ASTID | NonVirtualMethodCall.ASTID | VirtualMethodCall.ASTID => + Some(getCallees(basicBlock, pc)) + case Assignment.ASTID | ExprStmt.ASTID => + getExpression(statement).astID match { + case StaticFunctionCall.ASTID | NonVirtualFunctionCall.ASTID | + VirtualFunctionCall.ASTID => + Some(getCallees(basicBlock, pc)) + case _ => None + } + case _ => None } } implicit val ps = project.get(PropertyStoreKey) - - /** - * Gets the set of all methods possibly called at some call statement. - * - * @param basicBlock The basic block containing the call. - * @param pc The call's program counter. - * @return All methods possibly called at the statement index. - */ + implicit val ti: TypeIterator = new CHATypeIterator(project) + + /** Gets the set of all methods possibly called at some call statement. + * + * @param basicBlock + * The basic block containing the call. + * @param pc + * The call's program counter. + * @return + * All methods possibly called at the statement index. + */ def getCallees(basicBlock: BasicBlock, pc: Int)(implicit state: State): SomeSet[Method] = { val FinalEP(_, callees) = ps(declaredMethods(state.method), Callees.key) - definedMethods(callees.directCallees(pc)) + val context = ti.newContext(declaredMethods(state.method)) + definedMethods(callees.directCallees(context, pc).map(_.method)) } - /** - * Maps some declared methods to their defined methods. - * - * @param declaredMethods Some declared methods. - * @return All defined methods of `declaredMethods`. - */ + /** Maps some declared methods to their defined methods. + * + * @param declaredMethods + * Some declared methods. + * @return + * All defined methods of `declaredMethods`. + */ def definedMethods(declaredMethods: Iterator[DeclaredMethod]): SomeSet[Method] = { val result = scala.collection.mutable.Set.empty[Method] - declaredMethods.filter(declaredMethod ⇒ declaredMethod.hasSingleDefinedMethod || declaredMethod.hasMultipleDefinedMethods).foreach(declaredMethod ⇒ - declaredMethod.foreachDefinedMethod(defineMethod ⇒ result.add(defineMethod))) + declaredMethods + .filter(declaredMethod => + declaredMethod.hasSingleDefinedMethod || declaredMethod.hasMultipleDefinedMethods + ) + .foreach(declaredMethod => + declaredMethod.foreachDefinedMethod(defineMethod => result.add(defineMethod)) + ) result } - def reAnalyzeCalls(callSites: Set[(BasicBlock, Int)], callee: Method, fact: Option[DataFlowFact])(implicit state: State): Unit = { - val queue: mutable.Queue[(BasicBlock, Set[DataFlowFact], Option[Int], Option[Method], Option[DataFlowFact])] = + def reAnalyzeCalls(callSites: Set[(BasicBlock, Int)], callee: Method, fact: Option[DataFlowFact])( + implicit state: State + ): Unit = { + val queue: mutable.Queue[ + (BasicBlock, Set[DataFlowFact], Option[Int], Option[Method], Option[DataFlowFact]) + ] = mutable.Queue.empty - for ((block, index) ← callSites) - queue.enqueue( - ( - block, - state.incomingFacts(block), - Some(index), - Some(callee), - fact)) + for ((block, index) <- callSites) + queue.enqueue((block, state.incomingFacts(block), Some(index), Some(callee), fact)) process(queue) } /** See handleCallUpdate(e) */ // This is a copy of handleCallUpdate(e) that loops over a set of updates more efficiently. - def handleCallUpdates(es: Iterable[(DeclaredMethod, DataFlowFact)])(implicit state: State): Unit = { - val queue: mutable.Queue[(BasicBlock, Set[DataFlowFact], Option[Int], Option[Method], Option[DataFlowFact])] = + def handleCallUpdates( + es: Iterable[(DeclaredMethod, DataFlowFact)] + )(implicit state: State): Unit = { + val queue: mutable.Queue[ + (BasicBlock, Set[DataFlowFact], Option[Int], Option[Method], Option[DataFlowFact]) + ] = mutable.Queue.empty for ( - e ← es; + e <- es; blocks = state.pendingIfdsCallSites(e); - (block, callSite) ← blocks - ) queue.enqueue( - ( - block, - state.incomingFacts(block), - Some(callSite), - Some(e._1.definedMethod), - Some(e._2))) + (block, callSite) <- blocks + ) + queue.enqueue( + (block, state.incomingFacts(block), Some(callSite), Some(e._1.definedMethod), Some(e._2)) + ) process(queue) } - /** - * Processes a statement with a call. - * - * @param basicBlock The basic block that contains the statement - * @param call The call statement. - * @param callees All possible callees of the call. - * @param in The facts valid before the call statement. - * @param calleeWithUpdateFact If present, the `callees` will only be analyzed with this fact instead of the facts returned by callFlow. - * @return A map, mapping from each successor statement of the `call` to the facts valid at their start. - */ + /** Processes a statement with a call. + * + * @param basicBlock + * The basic block that contains the statement + * @param call + * The call statement. + * @param callees + * All possible callees of the call. + * @param in + * The facts valid before the call statement. + * @param calleeWithUpdateFact + * If present, the `callees` will only be analyzed with this fact instead of the facts returned + * by callFlow. + * @return + * A map, mapping from each successor statement of the `call` to the facts valid at their + * start. + */ def handleCall( - basicBlock: BasicBlock, - call: Statement, - callees: SomeSet[Method], - in: Set[DataFlowFact], - calleeWithUpdateFact: Option[DataFlowFact])(implicit state: State): Map[Statement, Set[DataFlowFact]] = { + basicBlock: BasicBlock, + call: Statement, + callees: SomeSet[Method], + in: Set[DataFlowFact], + calleeWithUpdateFact: Option[DataFlowFact] + )(implicit state: State): Map[Statement, Set[DataFlowFact]] = { val successors = successorStatements(call, basicBlock) // Facts valid at the start of each successor var summaryEdges: Map[Statement, Set[DataFlowFact]] = Map.empty // If calleeWithUpdateFact is present, this means that the basic block already has been analyzed with the `in` facts. if (calleeWithUpdateFact.isEmpty) - for (successor ← successors) { - summaryEdges += successor → propagateNullFact(in, callToReturnFlow(call, successor, in)) + for (successor <- successors) { + summaryEdges += successor -> propagateNullFact(in, callToReturnFlow(call, successor, in)) } - for (calledMethod ← callees) { + for (calledMethod <- callees) { val callee = declaredMethods(calledMethod) if (callee.definedMethod.isNative) { // We cannot analyze native methods. Let the concrete analysis decide what to do. for { - successor ← successors - } summaryEdges += successor → (summaryEdges(successor) ++ nativeCall(call, callee, successor, in)) + successor <- successors + } summaryEdges += successor -> (summaryEdges(successor) ++ nativeCall( + call, + callee, + successor, + in + )) } else { val callToStart = if (calleeWithUpdateFact.isDefined) calleeWithUpdateFact.toSet else propagateNullFact(in, callFlow(call, callee, in)) var allNewExitFacts: Map[Statement, Set[DataFlowFact]] = Map.empty // Collect exit facts for each input fact separately - for (fact ← callToStart) { + for (fact <- callToStart) { /* - * If this is a recursive call with the same input facts, we assume that the call only produces the facts that are already known. - * The call site is added to `pendingIfdsCallSites`, so that it will be re-evaluated if new output facts become known for the input fact. - */ + * If this is a recursive call with the same input facts, we assume that the call only produces the facts that are already known. + * The call site is added to `pendingIfdsCallSites`, so that it will be re-evaluated if new output facts become known for the input fact. + */ if ((calledMethod eq state.method) && fact == state.source._2) { val newDependee = - state.pendingIfdsCallSites.getOrElse(state.source, Set.empty) + ((basicBlock, call.index)) - state.pendingIfdsCallSites = state.pendingIfdsCallSites.updated(state.source, newDependee) + state.pendingIfdsCallSites + .getOrElse(state.source, Set.empty) + ((basicBlock, call.index)) + state.pendingIfdsCallSites = + state.pendingIfdsCallSites.updated(state.source, newDependee) allNewExitFacts = mergeMaps( allNewExitFacts, mergeMaps( collectResult(state.cfg.normalReturnNode), - collectResult(state.cfg.abnormalReturnNode))) + collectResult(state.cfg.abnormalReturnNode) + ) + ) } else { val e = (callee, fact) val c = cell(e) - val callFlows = if (c.isComplete) - FinalOutcome(c.getResult()) - else - NextOutcome(c.getResult()) + val callFlows = + if (c.isComplete) + FinalOutcome(c.getResult()) + else + NextOutcome(c.getResult()) val oldValue = state.pendingIfdsDependees.get(c) val oldExitFacts: Map[Statement, Set[DataFlowFact]] = oldValue match { - case Some(NextOutcome(p)) ⇒ p.flows - case _ ⇒ Map.empty + case Some(NextOutcome(p)) => p.flows + case _ => Map.empty } val exitFacts: Map[Statement, Set[DataFlowFact]] = callFlows match { - case FinalOutcome(p) ⇒ + case FinalOutcome(p) => val newDependee = state.pendingIfdsCallSites.getOrElse(e, Set.empty) - ((basicBlock, call.index)) state.pendingIfdsCallSites = state.pendingIfdsCallSites.updated(e, newDependee) state.pendingIfdsDependees -= c p.flows - case NextOutcome(p) ⇒ + case NextOutcome(p) => val newDependee = state.pendingIfdsCallSites.getOrElse(e, Set.empty) + ((basicBlock, call.index)) state.pendingIfdsCallSites = state.pendingIfdsCallSites.updated(e, newDependee) - state.pendingIfdsDependees += c → callFlows + state.pendingIfdsDependees += c -> callFlows p.flows } // Only process new facts that are not in `oldExitFacts` allNewExitFacts = mergeMaps(allNewExitFacts, mapDifference(exitFacts, oldExitFacts)) /* - * If new exit facts were discovered for the callee-fact-pair, all call sites depending on this pair have to be re-evaluated. - * oldValue is undefined if the callee-fact pair has not been queried before or returned a FinalEP. - */ + * If new exit facts were discovered for the callee-fact-pair, all call sites depending on this pair have to be re-evaluated. + * oldValue is undefined if the callee-fact pair has not been queried before or returned a FinalEP. + */ if (oldValue.isDefined && oldExitFacts != exitFacts) { reAnalyzeCalls(state.pendingIfdsCallSites(e), e._1.definedMethod, Some(e._2)) } @@ -557,107 +665,145 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis // Map facts valid on each exit statement of the callee back to the caller for { - successor ← successors if successor.node.isBasicBlock || successor.node.isNormalReturnExitNode - exitStatement ← allNewExitFacts.keys if exitStatement.stmt.astID == Return.ASTID || + successor <- successors + if successor.node.isBasicBlock || successor.node.isNormalReturnExitNode + exitStatement <- allNewExitFacts.keys if exitStatement.stmt.astID == Return.ASTID || exitStatement.stmt.astID == ReturnValue.ASTID - } summaryEdges += successor → (summaryEdges.getOrElse(successor, Set.empty[DataFlowFact]) ++ - returnFlow(call, callee, exitStatement, successor, allNewExitFacts.getOrElse(exitStatement, Set.empty))) + } summaryEdges += successor -> (summaryEdges.getOrElse( + successor, + Set.empty[DataFlowFact] + ) ++ + returnFlow( + call, + callee, + exitStatement, + successor, + allNewExitFacts.getOrElse(exitStatement, Set.empty) + )) for { - successor ← successors if successor.node.isCatchNode || successor.node.isAbnormalReturnExitNode - exitStatement ← allNewExitFacts.keys if exitStatement.stmt.astID != Return.ASTID && + successor <- successors + if successor.node.isCatchNode || successor.node.isAbnormalReturnExitNode + exitStatement <- allNewExitFacts.keys if exitStatement.stmt.astID != Return.ASTID && exitStatement.stmt.astID != ReturnValue.ASTID - } summaryEdges += successor → (summaryEdges.getOrElse(successor, Set.empty[DataFlowFact]) ++ - returnFlow(call, callee, exitStatement, successor, allNewExitFacts.getOrElse(exitStatement, Set.empty))) + } summaryEdges += successor -> (summaryEdges.getOrElse( + successor, + Set.empty[DataFlowFact] + ) ++ + returnFlow( + call, + callee, + exitStatement, + successor, + allNewExitFacts.getOrElse(exitStatement, Set.empty) + )) } } summaryEdges } - /** - * Determines the successor statements for one source statement. - * - * @param statement The source statement. - * @param basicBlock The basic block containing the source statement. - * @return All successors of `statement`. - */ - def successorStatements(statement: Statement, basicBlock: BasicBlock)(implicit state: State): Set[Statement] = { + /** Determines the successor statements for one source statement. + * + * @param statement + * The source statement. + * @param basicBlock + * The basic block containing the source statement. + * @return + * All successors of `statement`. + */ + def successorStatements(statement: Statement, basicBlock: BasicBlock)(implicit + state: State + ): Set[Statement] = { val index = statement.index - if (index == basicBlock.endPC) for (successorBlock ← basicBlock.successors) yield firstStatement(successorBlock) + if (index == basicBlock.endPC) + for (successorBlock <- basicBlock.successors) yield firstStatement(successorBlock) else { val nextIndex = index + 1 - Set(Statement(statement.method, basicBlock, statement.code(nextIndex), nextIndex, statement.code, statement.cfg)) + Set( + Statement( + statement.method, + basicBlock, + statement.code(nextIndex), + nextIndex, + statement.code, + statement.cfg + ) + ) } } - /** - * If `from` contains a null fact, it will be added to `to`. - * - * @param from The set, which may contain the null fact initially. - * @param to The set, to which the null fact may be added. - * @return `to` with the null fact added, if it is contained in `from`. - */ + /** If `from` contains a null fact, it will be added to `to`. + * + * @param from + * The set, which may contain the null fact initially. + * @param to + * The set, to which the null fact may be added. + * @return + * `to` with the null fact added, if it is contained in `from`. + */ def propagateNullFact(from: Set[DataFlowFact], to: Set[DataFlowFact]): Set[DataFlowFact] = { val nullFact = from.find(_.isInstanceOf[AbstractIFDSNullFact]) if (nullFact.isDefined) to + nullFact.get else to } - /** - * Merges two maps that have sets as values. - * - * @param map1 The first map. - * @param map2 The second map. - * @return A map containing the keys of both map. Each key is mapped to the union of both maps' values. - */ + /** Merges two maps that have sets as values. + * + * @param map1 + * The first map. + * @param map2 + * The second map. + * @return + * A map containing the keys of both map. Each key is mapped to the union of both maps' values. + */ def mergeMaps[S, T](map1: Map[S, Set[T]], map2: Map[S, Set[T]]): Map[S, Set[T]] = { var result = map1 - for ((key, values) ← map2) { + for ((key, values) <- map2) { result.get(key) match { - case Some(resultValues) ⇒ + case Some(resultValues) => if (resultValues.size > values.size) result = result.updated(key, resultValues ++ values) else result = result.updated(key, values ++ resultValues) - case None ⇒ + case None => result = result.updated(key, values) } } result } - /** - * Computes the difference of two maps that have sets as their values. - * - * @param minuend The map, from which elements will be removed. - * @param subtrahend The map, whose elements will be removed from `minuend`. - * @return A map, containing the keys and values of `minuend`. - * The values of the result only contain those elements not present in `subtrahend` for the same key. - */ + /** Computes the difference of two maps that have sets as their values. + * + * @param minuend + * The map, from which elements will be removed. + * @param subtrahend + * The map, whose elements will be removed from `minuend`. + * @return + * A map, containing the keys and values of `minuend`. The values of the result only contain + * those elements not present in `subtrahend` for the same key. + */ def mapDifference[S, T](minuend: Map[S, Set[T]], subtrahend: Map[S, Set[T]]): Map[S, Set[T]] = { var result = minuend - for ((key, values) ← subtrahend) { + for ((key, values) <- subtrahend) { result = result.updated(key, result(key) -- values) } result } - /** - * Gets the Call for a statement that contains a call (MethodCall Stmt or ExprStmt/Assigment - * with FunctionCall) - * @param stmt - * @return - */ + /** Gets the Call for a statement that contains a call (MethodCall Stmt or ExprStmt/Assigment with + * FunctionCall) + * @param stmt + * @return + */ def asCall(stmt: Stmt[V]): Call[V] = stmt.astID match { - case Assignment.ASTID ⇒ stmt.asAssignment.expr.asFunctionCall - case ExprStmt.ASTID ⇒ stmt.asExprStmt.expr.asFunctionCall - case _ ⇒ stmt.asMethodCall + case Assignment.ASTID => stmt.asAssignment.expr.asFunctionCall + case ExprStmt.ASTID => stmt.asExprStmt.expr.asFunctionCall + case _ => stmt.asMethodCall } - /** - * Gets the first statement of a BasicBlock or the first statement of the handler BasicBlock of - * a CatchNode. - */ + /** Gets the first statement of a BasicBlock or the first statement of the handler BasicBlock of a + * CatchNode. + */ @tailrec private def firstStatement(node: CFGNode)(implicit state: State): Statement = { if (node.isBasicBlock) { @@ -670,11 +816,12 @@ abstract class AbstractIFDSAnalysis[DataFlowFact <: AbstractIFDSFact](parallelis } else throw new IllegalArgumentException(s"Unknown node type: $node") } - /** - * Retrieves and commits the methods result as calculated for its declaring class type for the - * current DefinedMethod that represents the non-overwritten method in a subtype. - */ - def baseMethodResult(source: (DeclaredMethod, DataFlowFact)): Outcome[IFDSProperty[DataFlowFact]] = { + /** Retrieves and commits the methods result as calculated for its declaring class type for the + * current DefinedMethod that represents the non-overwritten method in a subtype. + */ + def baseMethodResult( + source: (DeclaredMethod, DataFlowFact) + ): Outcome[IFDSProperty[DataFlowFact]] = { // set up a dependency and return that we do not have computed any information at this point. val (declaredMethod, sourceFact) = source val dm = declaredMethod.asDefinedMethod @@ -696,22 +843,22 @@ object AbstractIFDSAnalysis { type V = DUVar[ValueInformation] case class Statement( - method: Method, - node: CFGNode, - stmt: Stmt[V], - index: Int, - code: Array[Stmt[V]], - cfg: CFG[Stmt[V], TACStmts[V]]) { + method: Method, + node: CFGNode, + stmt: Stmt[V], + index: Int, + code: Array[Stmt[V]], + cfg: CFG[Stmt[V], TACStmts[V]] + ) { override def hashCode(): Int = method.hashCode() * 31 + index override def equals(o: Any): Boolean = { o match { - case s: Statement ⇒ s.index == index && s.method == method - case _ ⇒ false + case s: Statement => s.index == index && s.method == method + case _ => false } } override def toString: String = s"${method.toJava}" } } - diff --git a/core/src/test/scala/com/phaller/rasync/test/opal/ifds/IFDSProperty.scala b/core/src/test/scala/com/phaller/rasync/test/opal/ifds/IFDSProperty.scala index 0ec3ee4..145d672 100644 --- a/core/src/test/scala/com/phaller/rasync/test/opal/ifds/IFDSProperty.scala +++ b/core/src/test/scala/com/phaller/rasync/test/opal/ifds/IFDSProperty.scala @@ -7,8 +7,9 @@ import org.opalj.tac.DUVar trait IFDSPropertyMetaInformation[DataFlowFact] extends PropertyMetaInformation -abstract class IFDSProperty[DataFlowFact] extends Property - with IFDSPropertyMetaInformation[DataFlowFact] { +abstract class IFDSProperty[DataFlowFact] + extends Property + with IFDSPropertyMetaInformation[DataFlowFact] { /** The type of the TAC domain. */ type V = DUVar[KnownTypedValue] @@ -16,9 +17,9 @@ abstract class IFDSProperty[DataFlowFact] extends Property def flows: Map[AbstractIFDSAnalysis.Statement, Set[DataFlowFact]] override def equals(that: Any): Boolean = that match { - case other: IFDSProperty[DataFlowFact] ⇒ flows == other.flows - case _ ⇒ false + case other: IFDSProperty[DataFlowFact] => flows == other.flows + case _ => false } override def hashCode(): Int = flows.hashCode() -} \ No newline at end of file +} diff --git a/core/src/test/scala/com/phaller/rasync/test/opal/ifds/TestTaintAnalysis.scala b/core/src/test/scala/com/phaller/rasync/test/opal/ifds/TestTaintAnalysis.scala index 7fd65ac..7470990 100644 --- a/core/src/test/scala/com/phaller/rasync/test/opal/ifds/TestTaintAnalysis.scala +++ b/core/src/test/scala/com/phaller/rasync/test/opal/ifds/TestTaintAnalysis.scala @@ -6,7 +6,7 @@ import java.net.URL import com.phaller.rasync.pool._ -import org.opalj.collection.immutable.RefArray +import org.opalj.br.FieldTypes import org.opalj.br.DeclaredMethod import org.opalj.br.ObjectType import org.opalj.br.Method @@ -43,23 +43,23 @@ case class FlowFact(flow: ListSet[Method]) extends Fact { override val hashCode: Int = { // HERE, a foldLeft introduces a lot of overhead due to (un)boxing. var r = 1 - flow.foreach(f ⇒ r = (r + f.hashCode()) * 31) + flow.foreach(f => r = (r + f.hashCode()) * 31) r } } case object NullFact extends Fact with AbstractIFDSNullFact -/** - * A simple IFDS taint analysis. - * - * @author Dominik Helm - */ +/** A simple IFDS taint analysis. + * + * @author + * Dominik Helm + */ class TestTaintAnalysis( - parallelism: Int = Runtime.getRuntime.availableProcessors(), - scheduling: SchedulingStrategy[IFDSProperty[Fact], (DeclaredMethod, Fact)])( - implicit - val project: SomeProject) extends AbstractIFDSAnalysis[Fact](parallelism, scheduling)(project) { + parallelism: Int = Runtime.getRuntime.availableProcessors(), + scheduling: SchedulingStrategy[IFDSProperty[Fact], (DeclaredMethod, Fact)] +)(implicit val project: SomeProject) + extends AbstractIFDSAnalysis[Fact](parallelism, scheduling)(project) { override val property: IFDSPropertyMetaInformation[Fact] = Taint @@ -77,9 +77,9 @@ class TestTaintAnalysis( override def normalFlow(stmt: Statement, succ: Statement, in: Set[Fact]): Set[Fact] = stmt.stmt.astID match { - case Assignment.ASTID ⇒ + case Assignment.ASTID => handleAssignment(stmt, stmt.stmt.asAssignment.expr, in) - /*case ArrayStore.ASTID ⇒ + /*case ArrayStore.ASTID => val store = stmt.stmt.asArrayStore val definedBy = store.arrayRef.asVar.definedBy val index = getConstValue(store.index, stmt.code) @@ -87,51 +87,49 @@ class TestTaintAnalysis( if (index.isDefined) // Taint known array index // Instead of using an iterator, we are going to use internal iteration // in ++ definedBy.iterator.map(ArrayElement(_, index.get)) - definedBy.foldLeft(in) { (c, n) ⇒ c + ArrayElement(n, index.get) } + definedBy.foldLeft(in) { (c, n) => c + ArrayElement(n, index.get) } else // Taint whole array if index is unknown // Instead of using an iterator, we are going to use internal iteration: // in ++ definedBy.iterator.map(Variable) - definedBy.foldLeft(in) { (c, n) ⇒ c + Variable(n) } + definedBy.foldLeft(in) { (c, n) => c + Variable(n) } else in*/ - case PutStatic.ASTID ⇒ + case PutStatic.ASTID => val put = stmt.stmt.asPutStatic if (isTainted(put.value, in)) in + StaticField(put.declaringClass, put.name) else in - /*case PutField.ASTID ⇒ + /*case PutField.ASTID => val put = stmt.stmt.asPutField if (isTainted(put.value, in)) in + StaticField(put.declaringClass, put.name) else in*/ - case PutField.ASTID ⇒ + case PutField.ASTID => val put = stmt.stmt.asPutField val definedBy = put.objRef.asVar.definedBy if (isTainted(put.value, in)) - definedBy.foldLeft(in) { (in, defSite) ⇒ + definedBy.foldLeft(in) { (in, defSite) => in + InstanceField(defSite, put.declaringClass, put.name) } else in - case _ ⇒ in + case _ => in } - /** - * Returns true if the expression contains a taint. - */ + /** Returns true if the expression contains a taint. + */ def isTainted(expr: Expr[V], in: Set[Fact]): Boolean = { expr.isVar && in.exists { - case Variable(index) ⇒ expr.asVar.definedBy.contains(index) - //case ArrayElement(index, _) ⇒ expr.asVar.definedBy.contains(index) - case InstanceField(index, _, _) ⇒ expr.asVar.definedBy.contains(index) - case _ ⇒ false + case Variable(index) => expr.asVar.definedBy.contains(index) + // case ArrayElement(index, _) => expr.asVar.definedBy.contains(index) + case InstanceField(index, _, _) => expr.asVar.definedBy.contains(index) + case _ => false } } - /** - * Returns the constant int value of an expression if it exists, None otherwise. - */ + /** Returns the constant int value of an expression if it exists, None otherwise. + */ /*def getConstValue(expr: Expr[V], code: Array[Stmt[V]]): Option[Int] = { if (expr.isIntConst) Some(expr.asIntConst.value) else if (expr.isVar) { // TODO The following looks optimizable! - val constVals = expr.asVar.definedBy.iterator.map[Option[Int]] { idx ⇒ + val constVals = expr.asVar.definedBy.iterator.map[Option[Int]] { idx => if (idx >= 0) { val stmt = code(idx) if (stmt.astID == Assignment.ASTID && stmt.asAssignment.expr.isIntConst) @@ -140,7 +138,7 @@ class TestTaintAnalysis( None } else None }.toIterable - if (constVals.forall(option ⇒ option.isDefined && option.get == constVals.head.get)) + if (constVals.forall(option => option.isDefined && option.get == constVals.head.get)) constVals.head else None } else None @@ -148,108 +146,120 @@ class TestTaintAnalysis( def handleAssignment(stmt: Statement, expr: Expr[V], in: Set[Fact]): Set[Fact] = expr.astID match { - case Var.ASTID ⇒ + case Var.ASTID => val newTaint = in.collect { - case Variable(index) if expr.asVar.definedBy.contains(index) ⇒ + case Variable(index) if expr.asVar.definedBy.contains(index) => Some(Variable(stmt.index)) - /*case ArrayElement(index, taintIndex) if expr.asVar.definedBy.contains(index) ⇒ + /*case ArrayElement(index, taintIndex) if expr.asVar.definedBy.contains(index) => Some(ArrayElement(stmt.index, taintIndex))*/ - case _ ⇒ None + case _ => None }.flatten in ++ newTaint - /*case ArrayLoad.ASTID ⇒ + /*case ArrayLoad.ASTID => val load = expr.asArrayLoad if (in.exists { // The specific array element may be tainted - case ArrayElement(index, taintedIndex) ⇒ + case ArrayElement(index, taintedIndex) => val element = getConstValue(load.index, stmt.code) load.arrayRef.asVar.definedBy.contains(index) && (element.isEmpty || taintedIndex == element.get) // Or the whole array - case Variable(index) ⇒ load.arrayRef.asVar.definedBy.contains(index) - case _ ⇒ false + case Variable(index) => load.arrayRef.asVar.definedBy.contains(index) + case _ => false }) in + Variable(stmt.index) else in*/ - case GetStatic.ASTID ⇒ + case GetStatic.ASTID => val get = expr.asGetStatic if (in.contains(StaticField(get.declaringClass, get.name))) in + Variable(stmt.index) else in - /*case GetField.ASTID ⇒ + /*case GetField.ASTID => val get = expr.asGetField if (in.contains(StaticField(get.declaringClass, get.name))) in + Variable(stmt.index) else in*/ - case GetField.ASTID ⇒ + case GetField.ASTID => val get = expr.asGetField - if (in.exists { - // The specific field may be tainted - case InstanceField(index, _, taintedField) ⇒ - taintedField == get.name && get.objRef.asVar.definedBy.contains(index) - // Or the whole object - case Variable(index) ⇒ get.objRef.asVar.definedBy.contains(index) - case _ ⇒ false - }) + if ( + in.exists { + // The specific field may be tainted + case InstanceField(index, _, taintedField) => + taintedField == get.name && get.objRef.asVar.definedBy.contains(index) + // Or the whole object + case Variable(index) => get.objRef.asVar.definedBy.contains(index) + case _ => false + } + ) in + Variable(stmt.index) else in - case _ ⇒ in + case _ => in } - override def callFlow( - stmt: Statement, - callee: DeclaredMethod, - in: Set[Fact]): Set[Fact] = { + override def callFlow(stmt: Statement, callee: DeclaredMethod, in: Set[Fact]): Set[Fact] = { val allParams = asCall(stmt.stmt).allParams if (callee.name == "sink") - if (in.exists { - case Variable(index) ⇒ - allParams.exists(p ⇒ p.asVar.definedBy.contains(index)) - case _ ⇒ false - }) { + if ( + in.exists { + case Variable(index) => + allParams.exists(p => p.asVar.definedBy.contains(index)) + case _ => false + } + ) { println(s"Found flow: $stmt") } - if (callee.name == "forName" && (callee.declaringClassType eq ObjectType.Class) && - callee.descriptor.parameterTypes == RefArray(ObjectType.String)) - if (in.exists { - case Variable(index) ⇒ - asCall(stmt.stmt).params.exists(p ⇒ p.asVar.definedBy.contains(index)) - case _ ⇒ false - }) { + if ( + callee.name == "forName" && (callee.declaringClassType eq ObjectType.Class) && + callee.descriptor.parameterTypes == FieldTypes(ObjectType.String) + ) + if ( + in.exists { + case Variable(index) => + asCall(stmt.stmt).params.exists(p => p.asVar.definedBy.contains(index)) + case _ => false + } + ) { println(s"Found flow: $stmt") } - if (true || (callee.descriptor.returnType eq ObjectType.Class) || + if ( + true || (callee.descriptor.returnType eq ObjectType.Class) || (callee.descriptor.returnType eq ObjectType.Object) || - (callee.descriptor.returnType eq ObjectType.String)) { + (callee.descriptor.returnType eq ObjectType.String) + ) { var facts = Set.empty[Fact] in.foreach { - case Variable(index) ⇒ // Taint formal parameter if actual parameter is tainted + case Variable(index) => // Taint formal parameter if actual parameter is tainted allParams.iterator.zipWithIndex.foreach { - case (param, pIndex) if param.asVar.definedBy.contains(index) ⇒ + case (param, pIndex) if param.asVar.definedBy.contains(index) => facts += Variable(paramToIndex(pIndex, !callee.definedMethod.isStatic)) - case _ ⇒ // Nothing to do + case _ => // Nothing to do } - /*case ArrayElement(index, taintedIndex) ⇒ + /*case ArrayElement(index, taintedIndex) => // Taint element of formal parameter if element of actual parameter is tainted allParams.zipWithIndex.collect { - case (param, pIndex) if param.asVar.definedBy.contains(index) ⇒ + case (param, pIndex) if param.asVar.definedBy.contains(index) => ArrayElement(paramToIndex(pIndex, !callee.definedMethod.isStatic), taintedIndex) }*/ - case InstanceField(index, declClass, taintedField) ⇒ + case InstanceField(index, declClass, taintedField) => // Taint field of formal parameter if field of actual parameter is tainted // Only if the formal parameter is of a type that may have that field! allParams.iterator.zipWithIndex.foreach { - case (param, pIndex) if param.asVar.definedBy.contains(index) && - (paramToIndex(pIndex, !callee.definedMethod.isStatic) != -1 || - classHierarchy.isSubtypeOf(declClass, callee.declaringClassType)) ⇒ - facts += InstanceField(paramToIndex(pIndex, !callee.definedMethod.isStatic), declClass, taintedField) - case _ ⇒ // Nothing to do + case (param, pIndex) + if param.asVar.definedBy.contains(index) && + (paramToIndex(pIndex, !callee.definedMethod.isStatic) != -1 || + classHierarchy.isSubtypeOf(declClass, callee.declaringClassType)) => + facts += InstanceField( + paramToIndex(pIndex, !callee.definedMethod.isStatic), + declClass, + taintedField + ) + case _ => // Nothing to do } - case sf: StaticField ⇒ + case sf: StaticField => facts += sf } facts @@ -257,11 +267,12 @@ class TestTaintAnalysis( } override def returnFlow( - stmt: Statement, - callee: DeclaredMethod, - exit: Statement, - succ: Statement, - in: Set[Fact]): Set[Fact] = { + stmt: Statement, + callee: DeclaredMethod, + exit: Statement, + succ: Statement, + in: Set[Fact] + ): Set[Fact] = { if (callee.name == "source" && stmt.stmt.astID == Assignment.ASTID) Set(Variable(stmt.index)) else if (callee.name == "sanitize") @@ -271,45 +282,46 @@ class TestTaintAnalysis( val allParams = call.allParams var flows: Set[Fact] = Set.empty in.foreach { - /*case ArrayElement(index, taintedIndex) if index < 0 && index > -100 ⇒ + /*case ArrayElement(index, taintedIndex) if index < 0 && index > -100 => // Taint element of actual parameter if element of formal parameter is tainted val param = allParams(paramToIndex(index, !callee.definedMethod.isStatic)) flows ++= param.asVar.definedBy.iterator.map(ArrayElement(_, taintedIndex))*/ - case InstanceField(index, declClass, taintedField) if index < 0 && index > -255 ⇒ + case InstanceField(index, declClass, taintedField) if index < 0 && index > -255 => // Taint field of actual parameter if field of formal parameter is tainted val param = allParams(paramToIndex(index, !callee.definedMethod.isStatic)) - param.asVar.definedBy.foreach { defSite ⇒ + param.asVar.definedBy.foreach { defSite => flows += InstanceField(defSite, declClass, taintedField) } - case sf: StaticField ⇒ + case sf: StaticField => flows += sf - case FlowFact(flow) ⇒ + case FlowFact(flow) => val newFlow = flow + stmt.method if (entryPoints.contains(declaredMethods(exit.method))) { - //println(s"flow: "+newFlow.map(_.toJava).mkString(", ")) + // println(s"flow: "+newFlow.map(_.toJava).mkString(", ")) } else { flows += FlowFact(newFlow) } - case _ ⇒ + case _ => } // Propagate taints of the return value if (exit.stmt.astID == ReturnValue.ASTID && stmt.stmt.astID == Assignment.ASTID) { val returnValue = exit.stmt.asReturnValue.expr.asVar in.foreach { - case Variable(index) if returnValue.definedBy.contains(index) ⇒ + case Variable(index) if returnValue.definedBy.contains(index) => flows += Variable(stmt.index) - /*case ArrayElement(index, taintedIndex) if returnValue.definedBy.contains(index) ⇒ + /*case ArrayElement(index, taintedIndex) if returnValue.definedBy.contains(index) => ArrayElement(stmt.index, taintedIndex)*/ - case InstanceField(index, declClass, taintedField) if returnValue.definedBy.contains(index) ⇒ + case InstanceField(index, declClass, taintedField) + if returnValue.definedBy.contains(index) => flows += InstanceField(stmt.index, declClass, taintedField) - case _ ⇒ // nothing to do + case _ => // nothing to do } } @@ -317,9 +329,8 @@ class TestTaintAnalysis( } } - /** - * Converts a parameter origin to the index in the parameter seq (and vice-versa). - */ + /** Converts a parameter origin to the index in the parameter seq (and vice-versa). + */ def paramToIndex(param: Int, includeThis: Boolean): Int = (if (includeThis) -1 else -2) - param @@ -327,20 +338,24 @@ class TestTaintAnalysis( val call = asCall(stmt.stmt) if (call.name == "sanitize") { in.filter { - case Variable(index) ⇒ - !(call.params ++ call.receiverOption).exists { p ⇒ + case Variable(index) => + !(call.params ++ call.receiverOption).exists { p => val definedBy = p.asVar.definedBy definedBy.size == 1 && definedBy.contains(index) } - case _ ⇒ true + case _ => true } - } else if (call.name == "forName" && (call.declaringClass eq ObjectType.Class) && - call.descriptor.parameterTypes == RefArray(ObjectType.String)) { - if (in.exists { - case Variable(index) ⇒ - asCall(stmt.stmt).params.exists(p ⇒ p.asVar.definedBy.contains(index)) - case _ ⇒ false - }) { + } else if ( + call.name == "forName" && (call.declaringClass eq ObjectType.Class) && + call.descriptor.parameterTypes == FieldTypes(ObjectType.String) + ) { + if ( + in.exists { + case Variable(index) => + asCall(stmt.stmt).params.exists(p => p.asVar.definedBy.contains(index)) + case _ => false + } + ) { /*if (entryPoints.contains(declaredMethods(stmt.method))) { println(s"flow: "+stmt.method.toJava) in @@ -354,33 +369,40 @@ class TestTaintAnalysis( } } - /** - * If forName is called, we add a FlowFact. - */ - override def nativeCall(statement: Statement, callee: DeclaredMethod, successor: Statement, in: Set[Fact]): Set[Fact] = { + /** If forName is called, we add a FlowFact. + */ + override def nativeCall( + statement: Statement, + callee: DeclaredMethod, + successor: Statement, + in: Set[Fact] + ): Set[Fact] = { /* val allParams = asCall(statement.stmt).allParams if (statement.stmt.astID == Assignment.ASTID && in.exists { - case Variable(index) ⇒ + case Variable(index) => allParams.zipWithIndex.exists { - case (param, _) if param.asVar.definedBy.contains(index) ⇒ true - case _ ⇒ false + case (param, _) if param.asVar.definedBy.contains(index) => true + case _ => false } - /*case ArrayElement(index, _) ⇒ + /*case ArrayElement(index, _) => allParams.zipWithIndex.exists { - case (param, _) if param.asVar.definedBy.contains(index) ⇒ true - case _ ⇒ false + case (param, _) if param.asVar.definedBy.contains(index) => true + case _ => false }*/ - case _ ⇒ false + case _ => false }) Set(Variable(statement.index)) - else*/ Set.empty + else*/ + Set.empty } val entryPoints: Map[DeclaredMethod, Fact] = (for { - m ← project.allMethodsWithBody + m <- project.allMethodsWithBody if (m.isPublic || m.isProtected) && (m.descriptor.returnType == ObjectType.Object || m.descriptor.returnType == ObjectType.Class) - index ← m.descriptor.parameterTypes.zipWithIndex.collect { case (pType, index) if pType == ObjectType.String ⇒ index } - } //yield (declaredMethods(m), null) - yield declaredMethods(m) → Variable(-2 - index)).toMap + index <- m.descriptor.parameterTypes.zipWithIndex.collect { + case (pType, index) if pType == ObjectType.String => index + } + } // yield (declaredMethods(m), null) + yield declaredMethods(m) -> Variable(-2 - index)).toMap } class Taint(val flows: Map[Statement, Set[Fact]]) extends IFDSProperty[Fact] { @@ -393,40 +415,40 @@ class Taint(val flows: Map[Statement, Set[Fact]]) extends IFDSProperty[Fact] { object Taint extends IFDSPropertyMetaInformation[Fact] { override type Self = Taint - val key: PropertyKey[Taint] = PropertyKey.create( - "TestTaint", - new Taint(Map.empty)) + val key: PropertyKey[Taint] = PropertyKey.create("TestTaint", new Taint(Map.empty)) } object TestTaintAnalysisRunner extends FunSuite { def main(args: Array[String]): Unit = { - val p0 = Project(new File(args(args.length - 1))) //bytecode.RTJar) + val p0 = Project(new File(args(args.length - 1))) // bytecode.RTJar) com.phaller.rasync.pool.SchedulingStrategy // Using PropertySore here is fine, it is not use during analysis p0.getOrCreateProjectInformationKeyInitializationData( PropertyStoreKey, - (context: List[PropertyStoreContext[AnyRef]]) ⇒ { + (context: List[PropertyStoreContext[AnyRef]]) => { implicit val lg: LogContext = p0.logContext val ps = PKESequentialPropertyStore(context: _*) PropertyStore.updateDebug(false) ps - }) + } + ) p0.getOrCreateProjectInformationKeyInitializationData( LazyDetachedTACAIKey, - (m: Method) ⇒ new PrimitiveTACAIDomain(p0, m)) + (m: Method) => new PrimitiveTACAIDomain(p0, m) + ) PerformanceEvaluation.time { val manager = p0.get(FPCFAnalysesManagerKey) manager.runAll(new CallGraphDeserializerScheduler(new File(args(args.length - 2)))) - } { t ⇒ println(s"CG took ${t.toSeconds}") } + } { t => println(s"CG took ${t.toSeconds}") } for ( - scheduling ← List( + scheduling <- List( new DefaultScheduling[IFDSProperty[Fact], (DeclaredMethod, Fact)], new SourcesWithManyTargetsFirst[IFDSProperty[Fact], (DeclaredMethod, Fact)], new SourcesWithManyTargetsLast[IFDSProperty[Fact], (DeclaredMethod, Fact)], @@ -435,16 +457,18 @@ object TestTaintAnalysisRunner extends FunSuite { new TargetsWithManyTargetsFirst[IFDSProperty[Fact], (DeclaredMethod, Fact)], new TargetsWithManyTargetsLast[IFDSProperty[Fact], (DeclaredMethod, Fact)], new SourcesWithManySourcesFirst[IFDSProperty[Fact], (DeclaredMethod, Fact)], - new SourcesWithManySourcesLast[IFDSProperty[Fact], (DeclaredMethod, Fact)]); - threads ← List(1, 2, 4, 8, 10, 16, 20, 32, 40) + new SourcesWithManySourcesLast[IFDSProperty[Fact], (DeclaredMethod, Fact)] + ); + threads <- List(1, 2, 4, 8, 10, 16, 20, 32, 40) ) { var result = 0 var analysis: TestTaintAnalysis = null var entryPoints: Map[DeclaredMethod, Fact] = null var ts: List[Long] = List.empty - for (i ← (0 until 5)) { + for (i <- (0 until 5)) { PerformanceEvaluation.time({ - implicit val p: Project[URL] = p0 //.recreate(k ⇒ k == PropertyStoreKey.uniqueId || k == DeclaredMethodsKey.uniqueId) + implicit val p: Project[URL] = + p0 // .recreate(k => k == PropertyStoreKey.uniqueId || k == DeclaredMethodsKey.uniqueId) Counter.reset() // From now on, we may access ps for read operations only @@ -454,17 +478,16 @@ object TestTaintAnalysisRunner extends FunSuite { entryPoints = analysis.entryPoints entryPoints.foreach(analysis.forceComputation) analysis.waitForCompletion() - }) { t ⇒ - + }) { t => result = 0 for { - e ← entryPoints - fact ← analysis.getResult(e).flows.values.flatten.toSet[Fact] + e <- entryPoints + fact <- analysis.getResult(e).flows.values.flatten.toSet[Fact] } { fact match { - case FlowFact(flow) ⇒ + case FlowFact(flow) => result += 1; println(s"flow: " + flow.map(_.toJava).mkString(", ")) - case _ ⇒ + case _ => } } println(Counter.toString) diff --git a/monte-carlo-npv/src/main/scala/com/phaller/rasync/npv/Distribution.scala b/monte-carlo-npv/src/main/scala/com/phaller/rasync/npv/Distribution.scala index c71b945..8f45d9b 100644 --- a/monte-carlo-npv/src/main/scala/com/phaller/rasync/npv/Distribution.scala +++ b/monte-carlo-npv/src/main/scala/com/phaller/rasync/npv/Distribution.scala @@ -14,8 +14,7 @@ class SingleValueDistribution(value: Double) extends Distribution { override def getMin(): Double = value } -class TriangleDistribution(min: Double, likely: Double, max: Double) - extends Distribution { +class TriangleDistribution(min: Double, likely: Double, max: Double) extends Distribution { assert(max >= likely) assert(likely >= min) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index c6af797..7a7c02e 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -1,8 +1,8 @@ import sbt._ object Dependencies { - lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5" % "test" - lazy val opalCommon = "de.opal-project" %% "common" % "3.0.0-SNAPSHOT" - lazy val opalTAC = "de.opal-project" %% "three-address-code" % "3.0.0-SNAPSHOT" % "test" - lazy val scalaMeter = "com.storm-enroute" %% "scalameter" % "0.9" + lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.9" % "test" + lazy val opalCommon = "de.opal-project" %% "common" % "5.0.0" + lazy val opalTAC = "de.opal-project" %% "three-address-code" % "5.0.0" % "test" + lazy val scalaMeter = "com.storm-enroute" %% "scalameter" % "0.21" } diff --git a/project/Util.scala b/project/Util.scala deleted file mode 100644 index 22fb9a5..0000000 --- a/project/Util.scala +++ /dev/null @@ -1,4 +0,0 @@ -object Util { - val buildScalaVersion = System.getProperty("scala.version", "2.12.5") - val javaVersion = System.getProperty("java.version") -} diff --git a/project/Version.scala b/project/Version.scala new file mode 100644 index 0000000..1c85562 --- /dev/null +++ b/project/Version.scala @@ -0,0 +1,4 @@ +object Version { + val buildScalaVersion = System.getProperty("scala.version", "2.13.15") + val buildJavaVersion = System.getProperty("java.version") +} diff --git a/project/build.properties b/project/build.properties index c091b86..0b699c3 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=0.13.16 +sbt.version=1.10.2 diff --git a/project/plugins.sbt b/project/plugins.sbt index 6096f08..4523517 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,6 +1,8 @@ -resolvers += Resolver.url("scoverage-bintray", url("https://dl.bintray.com/sksamuel/sbt-plugins/"))(Resolver.ivyStylePatterns) +resolvers += Resolver.url("scoverage-bintray", url("https://dl.bintray.com/sksamuel/sbt-plugins/"))( + Resolver.ivyStylePatterns +) addSbtPlugin("org.scoverage" %% "sbt-scoverage" % "1.5.1") -addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.1") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4") addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.4")