From 079ec9e5701653c96f5896215331787ad559060f Mon Sep 17 00:00:00 2001 From: Fabian Winter Date: Thu, 18 Sep 2025 16:46:06 +0200 Subject: [PATCH 01/17] Add cleaning to the PropertyStore * current version, todo: make stuff as args instead of using config * add cleaning-configuration via args * fix parameter * switched logic for cleanup to be able to disable it * cleanup --- .../org/opalj/support/info/Immutability.scala | 18 +++----- .../scala/org/opalj/support/info/Purity.scala | 14 +++--- .../scala/org/opalj/bytecode/package.scala | 4 +- .../org/opalj/cli/ClearPropertyKeysArg.scala | 18 ++++++++ .../org/opalj/cli/DisableCleanupArg.scala | 15 +++++++ .../org/opalj/cli/KeepPropertyKeysArg.scala | 20 +++++++++ .../org/opalj/fpcf/PhaseConfiguration.scala | 3 +- .../scala/org/opalj/fpcf/PropertyStore.scala | 6 +++ .../main/scala/org/opalj/fpcf/Schedule.scala | 6 ++- .../opalj/fpcf/par/PKECPropertyStore.scala | 17 ++++--- .../scheduling/MaximumPhaseScheduling.scala | 45 +++++++++++++++---- build.sbt | 5 +++ 12 files changed, 131 insertions(+), 40 deletions(-) create mode 100644 OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala create mode 100644 OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala create mode 100644 OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala index 0f7dfc02ef..691fb51ba5 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala @@ -4,7 +4,6 @@ package support package info import scala.language.postfixOps - import java.io.BufferedWriter import java.io.File import java.io.FileWriter @@ -13,13 +12,10 @@ import java.net.URL import java.text.SimpleDateFormat import java.util.Calendar import scala.collection.immutable.SortedSet - import com.typesafe.config.Config import com.typesafe.config.ConfigValueFactory - import org.rogach.scallop.flagConverter import org.rogach.scallop.stringConverter - import org.opalj.ai.cli.DomainArg import org.opalj.br.ClassType import org.opalj.br.Field @@ -54,14 +50,7 @@ import org.opalj.br.fpcf.properties.immutability.TypeImmutability import org.opalj.br.fpcf.properties.immutability.UnsafelyLazilyInitialized import org.opalj.bytecode.JDKArg import org.opalj.bytecode.JRELibraryFolder -import org.opalj.cli.AnalysisLevelArg -import org.opalj.cli.ChoiceArg -import org.opalj.cli.ConfigurationNameArg -import org.opalj.cli.LibraryArg -import org.opalj.cli.OutputDirArg -import org.opalj.cli.ParsedArg -import org.opalj.cli.PlainArg -import org.opalj.cli.ThreadsNumArg +import org.opalj.cli.{AnalysisLevelArg, ChoiceArg, ClearPropertyKeysArg, ConfigurationNameArg, DisableCleanupArg, KeepPropertyKeysArg, LibraryArg, OutputDirArg, ParsedArg, PlainArg, ThreadsNumArg} import org.opalj.fpcf.ComputationSpecification import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS @@ -132,7 +121,10 @@ object Immutability extends ProjectsAnalysisApplication { analysisLevelArg !, ignoreLazyInitializationArg !, ConfigurationNameArg !, - EscapeArg + EscapeArg, + DisableCleanupArg, + KeepPropertyKeysArg, + ClearPropertyKeysArg ) init() diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala index cde09842c7..0515ea52eb 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala @@ -4,11 +4,9 @@ package support package info import scala.language.postfixOps - import java.io.File import java.io.FileOutputStream import java.io.PrintWriter - import org.opalj.ai.cli.DomainArg import org.opalj.br.DeclaredMethod import org.opalj.br.DefinedMethod @@ -41,12 +39,7 @@ import org.opalj.br.fpcf.properties.Pure import org.opalj.br.fpcf.properties.SideEffectFree import org.opalj.bytecode.JDKArg import org.opalj.bytecode.JDKPackages -import org.opalj.cli.AnalysisLevelArg -import org.opalj.cli.ConfigurationNameArg -import org.opalj.cli.EagerArg -import org.opalj.cli.IndividualArg -import org.opalj.cli.OutputDirArg -import org.opalj.cli.PackagesArg +import org.opalj.cli.{AnalysisLevelArg, ClearPropertyKeysArg, ConfigurationNameArg, EagerArg, DisableCleanupArg, IndividualArg, KeepPropertyKeysArg, OutputDirArg, PackagesArg} import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.ComputationSpecification import org.opalj.fpcf.FinalEP @@ -94,7 +87,10 @@ object Purity extends ProjectsAnalysisApplication { RaterArg !, IndividualArg, OutputDirArg, - PackagesArg + PackagesArg, + DisableCleanupArg, + KeepPropertyKeysArg, + ClearPropertyKeysArg ) generalArgs( DomainArg diff --git a/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala b/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala index 4194b4523d..6e0a0ba432 100644 --- a/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala +++ b/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala @@ -117,7 +117,7 @@ package object bytecode { lazy val JRELibraryFolder: File = { val javaVersion = System.getProperty("java.version") if (javaVersion.startsWith("1.")) { - val sunBootClassPath = System.getProperties().getProperty("sun.boot.class.path") + val sunBootClassPath = System.getProperties.getProperty("sun.boot.class.path") val paths = sunBootClassPath.split(File.pathSeparator) paths.find(_.endsWith("rt.jar")) match { @@ -148,7 +148,7 @@ package object bytecode { val javaVersion = System.getProperty("java.version") if (javaVersion.startsWith("1.")) { - val sunBootClassPath = System.getProperties().getProperty("sun.boot.class.path") + val sunBootClassPath = System.getProperties.getProperty("sun.boot.class.path") val paths = sunBootClassPath.split(File.pathSeparator) paths.find(_.endsWith("rt.jar")) match { diff --git a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala new file mode 100644 index 0000000000..0e82b96755 --- /dev/null +++ b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala @@ -0,0 +1,18 @@ +package org.opalj.cli + +import com.typesafe.config.{Config, ConfigValueFactory} + +object ClearPropertyKeysArg extends ParsedArg[String, String] { + override val name: String = "clearPropertyKeys" + override val description: String = "List of Properties to keep at the end of the analysis" + override val defaultValue: Option[String] = None + override def apply(config: Config, value: Option[String]): Config = { + val keys = value.getOrElse(Nil) + config.withValue( + "org.opalj.fpcf.AnalysisScenario.ClearPropertyKeys", + ConfigValueFactory.fromAnyRef(keys) + ) + } + + override def parse(arg: String): String = arg.trim +} \ No newline at end of file diff --git a/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala new file mode 100644 index 0000000000..9862b4e2e5 --- /dev/null +++ b/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala @@ -0,0 +1,15 @@ +package org.opalj.cli + +import com.typesafe.config.{Config, ConfigValueFactory} + +object DisableCleanupArg extends PlainArg[Boolean] { + override val name: String = "disableCleanup" + override def description: String = "Disable cleanup of the PropertyStore inbetween phases" + override val defaultValue: Option[Boolean] = Some(false) + override def apply(config: Config, value: Option[Boolean]): Config = { + config.withValue( + "org.opalj.fpcf.AnalysisScenario.DisableCleanup", + ConfigValueFactory.fromAnyRef(!value.get) + ) + } +} diff --git a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala new file mode 100644 index 0000000000..2dd8bf8eef --- /dev/null +++ b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala @@ -0,0 +1,20 @@ +package org.opalj.cli + +import com.typesafe.config.{Config, ConfigValueFactory} + +object KeepPropertyKeysArg extends ParsedArg[String, String] { + override val name: String = "keepPropertyKeys" + override val description: String = "List of Properties to keep at the end of the analysis" + override val defaultValue: Option[String] = None + override def apply(config: Config, value: Option[String]): Config = { + val str = value.getOrElse("") + config.withValue( + "org.opalj.fpcf.AnalysisScenario.KeepPropertyKeys", + ConfigValueFactory.fromAnyRef(str) + ) + } + + override def parse(arg: String): String = arg.trim +} + + diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala index 62a0a2d3da..1d56d22497 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala @@ -4,5 +4,6 @@ package fpcf case class PhaseConfiguration[A]( propertyKinds: PropertyKindsConfiguration, - scheduled: List[ComputationSpecification[A]] + scheduled: List[ComputationSpecification[A]], + var toDelete: Set[Int] = Set.empty ) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala index 8f5935cfee..d2495dcfe9 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala @@ -136,6 +136,8 @@ abstract class PropertyStore { private[this] val externalInformation = new ConcurrentHashMap[AnyRef, AnyRef]() + var currentPhaseConf: Option[PhaseConfiguration[_]] = None + /** * Attaches or returns some information associated with the property store using a key object. * @@ -1012,6 +1014,10 @@ abstract class PropertyStore { case t: Throwable => collectAndThrowException(t) } } + + def setCurrentPhaseConfiguration[A](pc: PhaseConfiguration[A]): Unit = { + currentPhaseConf = Some(pc) + } } /** diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index c329afc031..d02fd57185 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -46,13 +46,17 @@ case class Schedule[A]( var allExecutedAnalyses: List[(ComputationSpecification[A], A)] = Nil batches.iterator.zipWithIndex foreach { batchId => - val (PhaseConfiguration(configuration, css), id) = batchId + val (PhaseConfiguration(configuration, css, toDelete), id) = batchId if (trace) { info("analysis progress", s"setting up analysis phase $id: $configuration") } time { ps.setupPhase(configuration) + ps.setCurrentPhaseConfiguration( + PhaseConfiguration(configuration, css, toDelete) + ) + afterPhaseSetup(configuration) afterPhaseSetup(configuration) assert(ps.isIdle, "the property store is not idle after phase setup") diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala index 9f5abcffe8..918da17675 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -4,19 +4,17 @@ package fpcf package par import scala.annotation.switch - import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ListBuffer import scala.util.control.ControlThrowable - import com.typesafe.config.Config - import org.opalj.control.foreachWithIndex import org.opalj.fpcf.PropertyKey.fallbackPropertyBasedOnPKId import org.opalj.log.LogContext +//import org.opalj.util.PerformanceEvaluation.memory /** * Yet another parallel property store. @@ -472,7 +470,7 @@ class PKECPropertyStore( threads.foreach { _.start } threads.foreach { _.join } if (doTerminate) { - if (exception ne null) throw exception; + if (exception ne null) throw exception else throw new InterruptedException } } @@ -519,12 +517,19 @@ class PKECPropertyStore( startThreads(new PartialPropertiesFinalizerThread(_)) subPhaseId += 1 - + clearObsoletePropertyKinds() ps(AnalysisKeyId).clear() } idle = true } + private def clearObsoletePropertyKinds(): Unit = { + currentPhaseConf match { + case Some(pc) => + pc.toDelete.foreach{ key => ps(key).clear() } + case None => + } + } private[this] val interimStates: Array[ArrayBuffer[EPKState]] = Array.fill(THREAD_COUNT)(null) @@ -577,7 +582,7 @@ class PKECPropertyStore( if (tasks.isEmpty) { val active = activeTasks.get() if (active == 0) { - return; + return } else { // try workstealing: val largestQueue = queues.maxBy(_.size()) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala index 246c39e490..bc00502c67 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala @@ -3,17 +3,16 @@ package org.opalj package fpcf package scheduling -import scala.collection.mutable - import com.typesafe.config.Config - import org.opalj.collection.IntIterator import org.opalj.fpcf.AnalysisScenario.ConfigKeyPrefix +import org.opalj.fpcf.scheduling.CleanupCalculation.{DisableCleanup, PropertiesToKeepKey, PropertiesToRemoveKey} import org.opalj.fpcf.scheduling.MultiplePhaseScheduling.AnalysisScheduleLazyTransformerInMultiplePhasesKey -import org.opalj.graphs.sccs -import org.opalj.graphs.topologicalSort -import org.opalj.log.LogContext -import org.opalj.log.OPALLogger +import org.opalj.graphs.{sccs, topologicalSort} +import org.opalj.log.{LogContext, OPALLogger} + +import scala.collection.mutable +import scala.collection.mutable.ListBuffer /** * Base class for scheduling strategies that create multiple computation phases. @@ -98,7 +97,31 @@ abstract class MultiplePhaseScheduling extends SchedulingStrategy { computePhase(ps, phaseAnalyses, remainingAnalyses) } - schedule + if(config.getBoolean(DisableCleanup)) calculateDeletions(schedule, ps, config) else schedule + } + + private def calculateDeletions[A](schedule: List[PhaseConfiguration[A]], ps: PropertyStore, config: Config): List[PhaseConfiguration[A]] = { + val pkToKeep = if(config.getString(PropertiesToKeepKey).trim.isEmpty) List.empty else config.getString(PropertiesToKeepKey).split(",").map(_.trim.toInt).toList + val pkToDelete = if(config.getString(PropertiesToRemoveKey).trim.isEmpty) List.empty else config.getString(PropertiesToRemoveKey).split(",").map(_.trim.toInt).toSet + val properties: mutable.Set[Int] = + schedule.iterator.flatMap(_.propertyKinds.propertyKindsComputedInThisPhase.map(key => key.id)).to(mutable.Set) + + val modifiedSchedule: ListBuffer[PhaseConfiguration[A]] = ListBuffer.from(schedule) + + var usedDependencies: Set[Int] = Set.empty + var alreadyDeleted: Set[Int] = Set.empty + for (elem <- modifiedSchedule.reverseIterator) { + + val currentSchedule = elem.scheduled + usedDependencies ++= currentSchedule.iterator.flatMap(_.uses(ps).iterator.map(_.pk.id)) + usedDependencies ++= pkToKeep + val remaining: Set[Int] = properties.toSet diff usedDependencies diff alreadyDeleted + + elem.toDelete = remaining + alreadyDeleted ++= remaining + } + modifiedSchedule.last.toDelete ++= pkToDelete + modifiedSchedule.toList } /** @@ -169,3 +192,9 @@ object MaximumPhaseScheduling extends MultiplePhaseScheduling { (initialPhaseDependencyGraph.toMap, initialPhaseIndexToAnalyses.toMap) } } + +object CleanupCalculation { + final val PropertiesToKeepKey = s"${ConfigKeyPrefix}KeepPropertyKeys" + final val PropertiesToRemoveKey = s"${ConfigKeyPrefix}RemovePropertyKeys" + final val DisableCleanup = s"${ConfigKeyPrefix}DisableCleanup" +} \ No newline at end of file diff --git a/build.sbt b/build.sbt index 17ee55a9bf..6dcbdda73f 100644 --- a/build.sbt +++ b/build.sbt @@ -608,3 +608,8 @@ ThisBuild / sonatypeCredentialHost := sonatypeCentralHost Test / publishArtifact := false ThisBuild / publishTo := sonatypePublishToBundle.value ThisBuild / pomExtra := MavenPublishing.pomNodeSeq() + + +/****** + * Settings set for benchmarking, allowing for generating heapdumps + ******/ \ No newline at end of file From e19a5b7a8a7291ca9d348ec01f85a96127a77d77 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Thu, 18 Sep 2025 18:48:08 +0200 Subject: [PATCH 02/17] Fix Nullpointer, streamline code --- .../scala/org/opalj/cli/ClearPropertyKeysArg.scala | 3 +-- .../scala/org/opalj/cli/DisableCleanupArg.scala | 2 +- .../scala/org/opalj/cli/KeepPropertyKeysArg.scala | 3 +-- .../fpcf/scheduling/MaximumPhaseScheduling.scala | 14 +++++++++++--- build.sbt | 7 +------ 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala index 0e82b96755..ed7d1f02e0 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala @@ -7,10 +7,9 @@ object ClearPropertyKeysArg extends ParsedArg[String, String] { override val description: String = "List of Properties to keep at the end of the analysis" override val defaultValue: Option[String] = None override def apply(config: Config, value: Option[String]): Config = { - val keys = value.getOrElse(Nil) config.withValue( "org.opalj.fpcf.AnalysisScenario.ClearPropertyKeys", - ConfigValueFactory.fromAnyRef(keys) + ConfigValueFactory.fromAnyRef(value.getOrElse("")) ) } diff --git a/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala index 9862b4e2e5..0821939787 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala @@ -9,7 +9,7 @@ object DisableCleanupArg extends PlainArg[Boolean] { override def apply(config: Config, value: Option[Boolean]): Config = { config.withValue( "org.opalj.fpcf.AnalysisScenario.DisableCleanup", - ConfigValueFactory.fromAnyRef(!value.get) + ConfigValueFactory.fromAnyRef(value.getOrElse(false)) ) } } diff --git a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala index 2dd8bf8eef..95c9564c01 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala @@ -7,10 +7,9 @@ object KeepPropertyKeysArg extends ParsedArg[String, String] { override val description: String = "List of Properties to keep at the end of the analysis" override val defaultValue: Option[String] = None override def apply(config: Config, value: Option[String]): Config = { - val str = value.getOrElse("") config.withValue( "org.opalj.fpcf.AnalysisScenario.KeepPropertyKeys", - ConfigValueFactory.fromAnyRef(str) + ConfigValueFactory.fromAnyRef(value.getOrElse("")) ) } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala index bc00502c67..b9545b2fa3 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala @@ -101,9 +101,17 @@ abstract class MultiplePhaseScheduling extends SchedulingStrategy { } private def calculateDeletions[A](schedule: List[PhaseConfiguration[A]], ps: PropertyStore, config: Config): List[PhaseConfiguration[A]] = { - val pkToKeep = if(config.getString(PropertiesToKeepKey).trim.isEmpty) List.empty else config.getString(PropertiesToKeepKey).split(",").map(_.trim.toInt).toList - val pkToDelete = if(config.getString(PropertiesToRemoveKey).trim.isEmpty) List.empty else config.getString(PropertiesToRemoveKey).split(",").map(_.trim.toInt).toSet - val properties: mutable.Set[Int] = + def getSetOrNilForProperty(propertyKey: String) = { + if (!config.hasPath(propertyKey)) Nil + else { + val rawValue = config.getString(propertyKey).trim + if (rawValue.isEmpty) Set.empty[Int] + else rawValue.split(",").map(_.trim.toInt).toSet + } + } + val pkToKeep = getSetOrNilForProperty(PropertiesToKeepKey) + val pkToDelete = getSetOrNilForProperty(PropertiesToRemoveKey) + val properties: mutable.Set[Int] = schedule.iterator.flatMap(_.propertyKinds.propertyKindsComputedInThisPhase.map(key => key.id)).to(mutable.Set) val modifiedSchedule: ListBuffer[PhaseConfiguration[A]] = ListBuffer.from(schedule) diff --git a/build.sbt b/build.sbt index 6dcbdda73f..f379d7123b 100644 --- a/build.sbt +++ b/build.sbt @@ -607,9 +607,4 @@ ThisBuild / publishMavenStyle.withRank(KeyRanks.Invisible) := true ThisBuild / sonatypeCredentialHost := sonatypeCentralHost Test / publishArtifact := false ThisBuild / publishTo := sonatypePublishToBundle.value -ThisBuild / pomExtra := MavenPublishing.pomNodeSeq() - - -/****** - * Settings set for benchmarking, allowing for generating heapdumps - ******/ \ No newline at end of file +ThisBuild / pomExtra := MavenPublishing.pomNodeSeq() \ No newline at end of file From 7de135ab9436bfed72f3e7fd2768927b150db67f Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Fri, 19 Sep 2025 23:25:59 +0200 Subject: [PATCH 03/17] incooperate feedback, revert some changes --- .../org/opalj/support/info/Immutability.scala | 16 +++++++++++++++- .../scala/org/opalj/support/info/Purity.scala | 12 +++++++++++- .../main/scala/org/opalj/bytecode/package.scala | 4 ++-- .../org/opalj/cli/ClearPropertyKeysArg.scala | 16 +++++++++------- .../scala/org/opalj/cli/DisableCleanupArg.scala | 6 ++++-- .../org/opalj/cli/KeepPropertyKeysArg.scala | 17 +++++++++-------- .../org/opalj/fpcf/PhaseConfiguration.scala | 2 +- .../main/scala/org/opalj/fpcf/Schedule.scala | 1 - .../org/opalj/fpcf/par/PKECPropertyStore.scala | 11 +++++++---- build.sbt | 2 +- 10 files changed, 59 insertions(+), 28 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala index 691fb51ba5..1c632f2c40 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala @@ -4,6 +4,7 @@ package support package info import scala.language.postfixOps + import java.io.BufferedWriter import java.io.File import java.io.FileWriter @@ -12,10 +13,13 @@ import java.net.URL import java.text.SimpleDateFormat import java.util.Calendar import scala.collection.immutable.SortedSet + import com.typesafe.config.Config import com.typesafe.config.ConfigValueFactory + import org.rogach.scallop.flagConverter import org.rogach.scallop.stringConverter + import org.opalj.ai.cli.DomainArg import org.opalj.br.ClassType import org.opalj.br.Field @@ -50,7 +54,17 @@ import org.opalj.br.fpcf.properties.immutability.TypeImmutability import org.opalj.br.fpcf.properties.immutability.UnsafelyLazilyInitialized import org.opalj.bytecode.JDKArg import org.opalj.bytecode.JRELibraryFolder -import org.opalj.cli.{AnalysisLevelArg, ChoiceArg, ClearPropertyKeysArg, ConfigurationNameArg, DisableCleanupArg, KeepPropertyKeysArg, LibraryArg, OutputDirArg, ParsedArg, PlainArg, ThreadsNumArg} +import org.opalj.cli.AnalysisLevelArg +import org.opalj.cli.ChoiceArg +import org.opalj.cli.ClearPropertyKeysArg +import org.opalj.cli.ConfigurationNameArg +import org.opalj.cli.DisableCleanupArg +import org.opalj.cli.KeepPropertyKeysArg +import org.opalj.cli.LibraryArg +import org.opalj.cli.OutputDirArg +import org.opalj.cli.ParsedArg +import org.opalj.cli.PlainArg +import org.opalj.cli.ThreadsNumArg import org.opalj.fpcf.ComputationSpecification import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala index 0515ea52eb..b462c2930c 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala @@ -4,9 +4,11 @@ package support package info import scala.language.postfixOps + import java.io.File import java.io.FileOutputStream import java.io.PrintWriter + import org.opalj.ai.cli.DomainArg import org.opalj.br.DeclaredMethod import org.opalj.br.DefinedMethod @@ -39,7 +41,15 @@ import org.opalj.br.fpcf.properties.Pure import org.opalj.br.fpcf.properties.SideEffectFree import org.opalj.bytecode.JDKArg import org.opalj.bytecode.JDKPackages -import org.opalj.cli.{AnalysisLevelArg, ClearPropertyKeysArg, ConfigurationNameArg, EagerArg, DisableCleanupArg, IndividualArg, KeepPropertyKeysArg, OutputDirArg, PackagesArg} +import org.opalj.cli.AnalysisLevelArg +import org.opalj.cli.ClearPropertyKeysArg +import org.opalj.cli.ConfigurationNameArg +import org.opalj.cli.DisableCleanupArg +import org.opalj.cli.EagerArg +import org.opalj.cli.IndividualArg +import org.opalj.cli.KeepPropertyKeysArg +import org.opalj.cli.OutputDirArg +import org.opalj.cli.PackagesArg import org.opalj.collection.immutable.IntTrieSet import org.opalj.fpcf.ComputationSpecification import org.opalj.fpcf.FinalEP diff --git a/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala b/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala index 6e0a0ba432..4194b4523d 100644 --- a/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala +++ b/OPAL/bi/src/main/scala/org/opalj/bytecode/package.scala @@ -117,7 +117,7 @@ package object bytecode { lazy val JRELibraryFolder: File = { val javaVersion = System.getProperty("java.version") if (javaVersion.startsWith("1.")) { - val sunBootClassPath = System.getProperties.getProperty("sun.boot.class.path") + val sunBootClassPath = System.getProperties().getProperty("sun.boot.class.path") val paths = sunBootClassPath.split(File.pathSeparator) paths.find(_.endsWith("rt.jar")) match { @@ -148,7 +148,7 @@ package object bytecode { val javaVersion = System.getProperty("java.version") if (javaVersion.startsWith("1.")) { - val sunBootClassPath = System.getProperties.getProperty("sun.boot.class.path") + val sunBootClassPath = System.getProperties().getProperty("sun.boot.class.path") val paths = sunBootClassPath.split(File.pathSeparator) paths.find(_.endsWith("rt.jar")) match { diff --git a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala index ed7d1f02e0..f7be6cb48a 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala @@ -1,17 +1,19 @@ -package org.opalj.cli +package org.opalj +package cli -import com.typesafe.config.{Config, ConfigValueFactory} +import com.typesafe.config.Config +import com.typesafe.config.ConfigValueFactory -object ClearPropertyKeysArg extends ParsedArg[String, String] { +object ClearPropertyKeysArg extends ParsedArg[List[String], List[String]] { override val name: String = "clearPropertyKeys" override val description: String = "List of Properties to keep at the end of the analysis" - override val defaultValue: Option[String] = None - override def apply(config: Config, value: Option[String]): Config = { + override val defaultValue: Option[List[String]] = None + override def apply(config: Config, value: Option[List[String]]): Config = { config.withValue( "org.opalj.fpcf.AnalysisScenario.ClearPropertyKeys", ConfigValueFactory.fromAnyRef(value.getOrElse("")) ) } - override def parse(arg: String): String = arg.trim -} \ No newline at end of file + override def parse(arg: List[String]): List[String] = ??? +} diff --git a/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala index 0821939787..9902641684 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala @@ -1,6 +1,8 @@ -package org.opalj.cli +package org.opalj +package cli -import com.typesafe.config.{Config, ConfigValueFactory} +import com.typesafe.config.Config +import com.typesafe.config.ConfigValueFactory object DisableCleanupArg extends PlainArg[Boolean] { override val name: String = "disableCleanup" diff --git a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala index 95c9564c01..be6d6bb6dc 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala @@ -1,19 +1,20 @@ -package org.opalj.cli +package org.opalj +package cli -import com.typesafe.config.{Config, ConfigValueFactory} +import com.typesafe.config.Config +import com.typesafe.config.ConfigValueFactory -object KeepPropertyKeysArg extends ParsedArg[String, String] { +object KeepPropertyKeysArg extends ParsedArg[List[String], List[String]] { override val name: String = "keepPropertyKeys" override val description: String = "List of Properties to keep at the end of the analysis" - override val defaultValue: Option[String] = None - override def apply(config: Config, value: Option[String]): Config = { + override val defaultValue: Option[List[String]] = None + + override def apply(config: Config, value: Option[List[String]]): Config = { config.withValue( "org.opalj.fpcf.AnalysisScenario.KeepPropertyKeys", ConfigValueFactory.fromAnyRef(value.getOrElse("")) ) } - override def parse(arg: String): String = arg.trim + override def parse(arg: List[String]): List[String] = ??? } - - diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala index 1d56d22497..08ee5c98ca 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PhaseConfiguration.scala @@ -5,5 +5,5 @@ package fpcf case class PhaseConfiguration[A]( propertyKinds: PropertyKindsConfiguration, scheduled: List[ComputationSpecification[A]], - var toDelete: Set[Int] = Set.empty + toDelete: Set[Int] = Set.empty ) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index d02fd57185..954bd71b94 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -57,7 +57,6 @@ case class Schedule[A]( PhaseConfiguration(configuration, css, toDelete) ) afterPhaseSetup(configuration) - afterPhaseSetup(configuration) assert(ps.isIdle, "the property store is not idle after phase setup") var executedAnalyses: List[(ComputationSpecification[A], A)] = Nil diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala index 918da17675..c5d99c7f9a 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -4,13 +4,16 @@ package fpcf package par import scala.annotation.switch + import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.atomic.AtomicInteger import scala.collection.mutable.ArrayBuffer import scala.collection.mutable.ListBuffer import scala.util.control.ControlThrowable + import com.typesafe.config.Config + import org.opalj.control.foreachWithIndex import org.opalj.fpcf.PropertyKey.fallbackPropertyBasedOnPKId import org.opalj.log.LogContext @@ -470,7 +473,7 @@ class PKECPropertyStore( threads.foreach { _.start } threads.foreach { _.join } if (doTerminate) { - if (exception ne null) throw exception + if (exception ne null) throw exception; else throw new InterruptedException } } @@ -523,11 +526,11 @@ class PKECPropertyStore( idle = true } + private def clearObsoletePropertyKinds(): Unit = { currentPhaseConf match { case Some(pc) => - pc.toDelete.foreach{ key => ps(key).clear() } - case None => + pc.toDelete.foreach { key => ps(key).clear() } } } @@ -582,7 +585,7 @@ class PKECPropertyStore( if (tasks.isEmpty) { val active = activeTasks.get() if (active == 0) { - return + return; } else { // try workstealing: val largestQueue = queues.maxBy(_.size()) diff --git a/build.sbt b/build.sbt index f379d7123b..17ee55a9bf 100644 --- a/build.sbt +++ b/build.sbt @@ -607,4 +607,4 @@ ThisBuild / publishMavenStyle.withRank(KeyRanks.Invisible) := true ThisBuild / sonatypeCredentialHost := sonatypeCentralHost Test / publishArtifact := false ThisBuild / publishTo := sonatypePublishToBundle.value -ThisBuild / pomExtra := MavenPublishing.pomNodeSeq() \ No newline at end of file +ThisBuild / pomExtra := MavenPublishing.pomNodeSeq() From e1c45108f1f234fe1666a549158af259ee35b019 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Thu, 2 Oct 2025 02:03:53 +0200 Subject: [PATCH 04/17] fixes, no longer expect ids in parameters --- .../scala/org/opalj/fpcf/PropertyStore.scala | 10 +-- .../main/scala/org/opalj/fpcf/Schedule.scala | 7 +- .../opalj/fpcf/par/PKECPropertyStore.scala | 8 +- .../scheduling/MaximumPhaseScheduling.scala | 78 ++++++++++++------- 4 files changed, 59 insertions(+), 44 deletions(-) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala index d2495dcfe9..fe62702f54 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala @@ -135,8 +135,7 @@ abstract class PropertyStore { // private[this] val externalInformation = new ConcurrentHashMap[AnyRef, AnyRef]() - - var currentPhaseConf: Option[PhaseConfiguration[_]] = None + protected[this] var currentPhaseToDelete: Set[Int] = Set.empty /** * Attaches or returns some information associated with the property store using a key object. @@ -531,7 +530,8 @@ abstract class PropertyStore { pc: EOptionP[E, P] => InterimEP[E, P] ): Unit - final def setupPhase(configuration: PropertyKindsConfiguration): Unit = { + final def setupPhase(configuration: PropertyKindsConfiguration, toDelete: Set[Int]): Unit = { + this.currentPhaseToDelete = toDelete setupPhase( configuration.propertyKindsComputedInThisPhase, configuration.propertyKindsComputedInLaterPhase, @@ -1014,10 +1014,6 @@ abstract class PropertyStore { case t: Throwable => collectAndThrowException(t) } } - - def setCurrentPhaseConfiguration[A](pc: PhaseConfiguration[A]): Unit = { - currentPhaseConf = Some(pc) - } } /** diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index 954bd71b94..b9c84149d8 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -52,10 +52,7 @@ case class Schedule[A]( info("analysis progress", s"setting up analysis phase $id: $configuration") } time { - ps.setupPhase(configuration) - ps.setCurrentPhaseConfiguration( - PhaseConfiguration(configuration, css, toDelete) - ) + ps.setupPhase(configuration, toDelete) afterPhaseSetup(configuration) assert(ps.isIdle, "the property store is not idle after phase setup") @@ -91,7 +88,7 @@ case class Schedule[A]( } } // ... we are done now; the computed properties will no longer be computed! - ps.setupPhase(Set.empty, Set.empty) + ps.setupPhase(Set.empty, propertyKindsComputedInLaterPhase = Set.empty) allExecutedAnalyses } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala index c5d99c7f9a..662dad195c 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -17,7 +17,6 @@ import com.typesafe.config.Config import org.opalj.control.foreachWithIndex import org.opalj.fpcf.PropertyKey.fallbackPropertyBasedOnPKId import org.opalj.log.LogContext -//import org.opalj.util.PerformanceEvaluation.memory /** * Yet another parallel property store. @@ -520,18 +519,15 @@ class PKECPropertyStore( startThreads(new PartialPropertiesFinalizerThread(_)) subPhaseId += 1 - clearObsoletePropertyKinds() ps(AnalysisKeyId).clear() } + clearObsoletePropertyKinds() idle = true } private def clearObsoletePropertyKinds(): Unit = { - currentPhaseConf match { - case Some(pc) => - pc.toDelete.foreach { key => ps(key).clear() } - } + currentPhaseToDelete.foreach { key => ps(key).clear() } } private[this] val interimStates: Array[ArrayBuffer[EPKState]] = diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala index b9545b2fa3..250af136ac 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala @@ -3,16 +3,21 @@ package org.opalj package fpcf package scheduling +import scala.collection.mutable +import scala.collection.mutable.ListBuffer + import com.typesafe.config.Config + import org.opalj.collection.IntIterator import org.opalj.fpcf.AnalysisScenario.ConfigKeyPrefix -import org.opalj.fpcf.scheduling.CleanupCalculation.{DisableCleanup, PropertiesToKeepKey, PropertiesToRemoveKey} +import org.opalj.fpcf.scheduling.CleanupCalculation.DisableCleanup +import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToKeepKey +import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToRemoveKey import org.opalj.fpcf.scheduling.MultiplePhaseScheduling.AnalysisScheduleLazyTransformerInMultiplePhasesKey -import org.opalj.graphs.{sccs, topologicalSort} -import org.opalj.log.{LogContext, OPALLogger} - -import scala.collection.mutable -import scala.collection.mutable.ListBuffer +import org.opalj.graphs.sccs +import org.opalj.graphs.topologicalSort +import org.opalj.log.LogContext +import org.opalj.log.OPALLogger /** * Base class for scheduling strategies that create multiple computation phases. @@ -97,38 +102,59 @@ abstract class MultiplePhaseScheduling extends SchedulingStrategy { computePhase(ps, phaseAnalyses, remainingAnalyses) } - if(config.getBoolean(DisableCleanup)) calculateDeletions(schedule, ps, config) else schedule + if (!config.getBoolean(DisableCleanup)) calculateDeletions(schedule, ps, config) else schedule } - private def calculateDeletions[A](schedule: List[PhaseConfiguration[A]], ps: PropertyStore, config: Config): List[PhaseConfiguration[A]] = { - def getSetOrNilForProperty(propertyKey: String) = { - if (!config.hasPath(propertyKey)) Nil - else { - val rawValue = config.getString(propertyKey).trim - if (rawValue.isEmpty) Set.empty[Int] - else rawValue.split(",").map(_.trim.toInt).toSet + private def calculateDeletions[A]( + schedule: List[PhaseConfiguration[A]], + ps: PropertyStore, + config: Config + ): List[PhaseConfiguration[A]] = { + def keyByName(name: String): SomePropertyKey = { + val max = PropertyKey.maxId + var i = 0 + while (i <= max) { + if (PropertyKey.name(i) == name) return PropertyKey.key(i) + i += 1 } + throw new IllegalArgumentException(s"Unknown property name: $name") + } + def getSetForProperty(propertyKey: String): Set[Int] = { + Option( + config.getString(propertyKey) + ).toSeq.flatMap(_.split(",")).filter(_.nonEmpty).map(name => keyByName(name).id).toSet } - val pkToKeep = getSetOrNilForProperty(PropertiesToKeepKey) - val pkToDelete = getSetOrNilForProperty(PropertiesToRemoveKey) - val properties: mutable.Set[Int] = - schedule.iterator.flatMap(_.propertyKinds.propertyKindsComputedInThisPhase.map(key => key.id)).to(mutable.Set) + val pkToKeep = getSetForProperty(PropertiesToKeepKey) + val pkToDelete = getSetForProperty(PropertiesToRemoveKey) + val properties: Set[Int] = + schedule.iterator.flatMap( + _.propertyKinds.propertyKindsComputedInThisPhase.map(_.id) + ).toSet val modifiedSchedule: ListBuffer[PhaseConfiguration[A]] = ListBuffer.from(schedule) var usedDependencies: Set[Int] = Set.empty var alreadyDeleted: Set[Int] = Set.empty - for (elem <- modifiedSchedule.reverseIterator) { - + val lastIndex = modifiedSchedule.length - 1 + var index = lastIndex + while (index >= 0) { + val elem = modifiedSchedule(index) val currentSchedule = elem.scheduled - usedDependencies ++= currentSchedule.iterator.flatMap(_.uses(ps).iterator.map(_.pk.id)) + usedDependencies ++= currentSchedule.iterator.flatMap(_.uses(ps).iterator.map(_.pk.id)).toSet usedDependencies ++= pkToKeep - val remaining: Set[Int] = properties.toSet diff usedDependencies diff alreadyDeleted + val remaining: Set[Int] = { + if (index == lastIndex) { + (properties -- pkToKeep) -- alreadyDeleted + } else + (properties -- usedDependencies) -- alreadyDeleted + } - elem.toDelete = remaining alreadyDeleted ++= remaining + index -= 1 } - modifiedSchedule.last.toDelete ++= pkToDelete + modifiedSchedule(lastIndex) = modifiedSchedule(lastIndex).copy( + toDelete = (modifiedSchedule(lastIndex).toDelete ++ pkToDelete) -- pkToKeep + ) modifiedSchedule.toList } @@ -203,6 +229,6 @@ object MaximumPhaseScheduling extends MultiplePhaseScheduling { object CleanupCalculation { final val PropertiesToKeepKey = s"${ConfigKeyPrefix}KeepPropertyKeys" - final val PropertiesToRemoveKey = s"${ConfigKeyPrefix}RemovePropertyKeys" + final val PropertiesToRemoveKey = s"${ConfigKeyPrefix}ClearPropertyKeys" final val DisableCleanup = s"${ConfigKeyPrefix}DisableCleanup" -} \ No newline at end of file +} From 180be94540fbcc4bcde4e32d93007fb6c35bed41 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Thu, 2 Oct 2025 03:36:13 +0200 Subject: [PATCH 05/17] add cleanup to PKESequentialPropertyStore --- .../org/opalj/fpcf/seq/PKESequentialPropertyStore.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala index f6846ee97f..c7475d1bf7 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala @@ -825,11 +825,17 @@ final class PKESequentialPropertyStore protected ( } } while (continueComputation) + clearObsoletePropertyKinds() idle = true if (exception != null) throw exception; } + private def clearObsoletePropertyKinds(): Unit = { + currentPhaseToDelete.foreach { key => ps(key).clear() } + } + + def shutdown(): Unit = {} } From 885d23fa5083187c052841631417960199b7a9ef Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Thu, 2 Oct 2025 03:52:47 +0200 Subject: [PATCH 06/17] formatting --- .../scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala index c7475d1bf7..09f405ff55 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala @@ -835,7 +835,6 @@ final class PKESequentialPropertyStore protected ( currentPhaseToDelete.foreach { key => ps(key).clear() } } - def shutdown(): Unit = {} } From ef9b83f9bdcc1a9b1b5e7ee45176a633a86dc452 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Sun, 5 Oct 2025 01:29:52 +0200 Subject: [PATCH 07/17] add programmatical way to setup cleanup --- .../org/opalj/cli/ClearPropertyKeysArg.scala | 5 +- .../org/opalj/cli/KeepPropertyKeysArg.scala | 4 +- .../scala/org/opalj/fpcf/PropertyKey.scala | 7 ++ .../scala/org/opalj/fpcf/PropertyStore.scala | 29 ++++++-- .../main/scala/org/opalj/fpcf/Schedule.scala | 14 +++- .../opalj/fpcf/par/PKECPropertyStore.scala | 5 +- .../org/opalj/fpcf/scheduling/Cleanup.scala | 71 +++++++++++++++++++ .../scheduling/MaximumPhaseScheduling.scala | 57 ++------------- .../fpcf/seq/PKESequentialPropertyStore.scala | 4 +- 9 files changed, 128 insertions(+), 68 deletions(-) create mode 100644 OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala diff --git a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala index f7be6cb48a..a324094af8 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala @@ -8,6 +8,7 @@ object ClearPropertyKeysArg extends ParsedArg[List[String], List[String]] { override val name: String = "clearPropertyKeys" override val description: String = "List of Properties to keep at the end of the analysis" override val defaultValue: Option[List[String]] = None + override def apply(config: Config, value: Option[List[String]]): Config = { config.withValue( "org.opalj.fpcf.AnalysisScenario.ClearPropertyKeys", @@ -15,5 +16,7 @@ object ClearPropertyKeysArg extends ParsedArg[List[String], List[String]] { ) } - override def parse(arg: List[String]): List[String] = ??? + override def parse(arg: List[String]): List[String] = { + arg.flatMap(_.split(",")) + } } diff --git a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala index be6d6bb6dc..158b57d149 100644 --- a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala +++ b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala @@ -16,5 +16,7 @@ object KeepPropertyKeysArg extends ParsedArg[List[String], List[String]] { ) } - override def parse(arg: List[String]): List[String] = ??? + override def parse(arg: List[String]): List[String] = { + arg.flatMap(_.split(",")) + } } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala index dac81ab898..5c5213d84b 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala @@ -111,6 +111,10 @@ object PropertyKey { create(name, fpc) } + lazy val byName: Map[String, SomePropertyKey] = { + propertyKeys.iterator.map { k => PropertyKey.name(k.id) -> k }.toMap + } + // // Query the core properties of each property kind // =============================================== @@ -123,6 +127,9 @@ object PropertyKey { */ def name(id: Int): String = propertyKeyNames(id) + def idByName(name: String): Int = + byName.getOrElse(name, throw new IllegalArgumentException(s"Unknown property name: $name")).id + final def name(pKind: PropertyKind): String = name(pKind.id) final def name(eOptionP: SomeEOptionP): String = name(eOptionP.pk.id) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala index fe62702f54..45663e0a5d 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala @@ -135,7 +135,7 @@ abstract class PropertyStore { // private[this] val externalInformation = new ConcurrentHashMap[AnyRef, AnyRef]() - protected[this] var currentPhaseToDelete: Set[Int] = Set.empty + protected[this] var currentPhaseDeletionMask: Array[Boolean] = Array.fill(PropertyKey.maxId + 1)(false) /** * Attaches or returns some information associated with the property store using a key object. @@ -500,6 +500,19 @@ abstract class PropertyStore { protected[this] def doSet(e: Entity, p: Property): Unit + protected[fpcf] final def clearObsoletePropertyKinds(): Unit = { + val mask = currentPhaseDeletionMask + var index = 0 + while (index < mask.length) { + if (mask(index)) { + clearSlot(index) + } + index += 1 + } + } + + protected def clearSlot(id: Int): Unit + /** * Associates the given entity with the newly computed intermediate property P. * @@ -530,13 +543,13 @@ abstract class PropertyStore { pc: EOptionP[E, P] => InterimEP[E, P] ): Unit - final def setupPhase(configuration: PropertyKindsConfiguration, toDelete: Set[Int]): Unit = { - this.currentPhaseToDelete = toDelete + final def setupPhase(configuration: PropertyKindsConfiguration): Unit = { setupPhase( configuration.propertyKindsComputedInThisPhase, configuration.propertyKindsComputedInLaterPhase, configuration.suppressInterimUpdates, - configuration.collaborativelyComputedPropertyKindsFinalizationOrder + configuration.collaborativelyComputedPropertyKindsFinalizationOrder, + toDelete = Set.empty ) } @@ -572,7 +585,8 @@ abstract class PropertyStore { propertyKindsComputedInThisPhase: Set[PropertyKind], propertyKindsComputedInLaterPhase: Set[PropertyKind] = Set.empty, suppressInterimUpdates: Map[PropertyKind, Set[PropertyKind]] = Map.empty, - finalizationOrder: List[List[PropertyKind]] = List.empty + finalizationOrder: List[List[PropertyKind]] = List.empty, + toDelete: Set[Int] = Set.empty ): Unit = handleExceptions { if (!isIdle) { throw new IllegalStateException("computations are already running"); @@ -586,6 +600,11 @@ abstract class PropertyStore { "illegal self dependency" ) + val mask = currentPhaseDeletionMask + java.util.Arrays.fill(mask, false) + + toDelete.foreach(id => mask(id) = true) + // Step 1 // Copy all property kinds that were computed in the previous phase that are no // longer computed to the "propertyKindsComputedInEarlierPhase" array. diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index b9c84149d8..fe80c2ab97 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -46,13 +46,21 @@ case class Schedule[A]( var allExecutedAnalyses: List[(ComputationSpecification[A], A)] = Nil batches.iterator.zipWithIndex foreach { batchId => - val (PhaseConfiguration(configuration, css, toDelete), id) = batchId + val (phase, id) = batchId + val configuration = phase.propertyKinds + val css = phase.scheduled if (trace) { info("analysis progress", s"setting up analysis phase $id: $configuration") } time { - ps.setupPhase(configuration, toDelete) + ps.setupPhase( + configuration.propertyKindsComputedInThisPhase, + configuration.propertyKindsComputedInLaterPhase, + configuration.suppressInterimUpdates, + configuration.collaborativelyComputedPropertyKindsFinalizationOrder, + phase.toDelete + ) afterPhaseSetup(configuration) assert(ps.isIdle, "the property store is not idle after phase setup") @@ -88,7 +96,7 @@ case class Schedule[A]( } } // ... we are done now; the computed properties will no longer be computed! - ps.setupPhase(Set.empty, propertyKindsComputedInLaterPhase = Set.empty) + ps.setupPhase(Set.empty, Set.empty) allExecutedAnalyses } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala index 662dad195c..fb24910076 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -17,6 +17,7 @@ import com.typesafe.config.Config import org.opalj.control.foreachWithIndex import org.opalj.fpcf.PropertyKey.fallbackPropertyBasedOnPKId import org.opalj.log.LogContext +import org.opalj.util.PerformanceEvaluation.memory /** * Yet another parallel property store. @@ -526,9 +527,7 @@ class PKECPropertyStore( idle = true } - private def clearObsoletePropertyKinds(): Unit = { - currentPhaseToDelete.foreach { key => ps(key).clear() } - } + override protected def clearSlot(id: Int): Unit = ps(id).clear() private[this] val interimStates: Array[ArrayBuffer[EPKState]] = Array.fill(THREAD_COUNT)(null) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala new file mode 100644 index 0000000000..15189102f4 --- /dev/null +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala @@ -0,0 +1,71 @@ +package org.opalj +package fpcf +package scheduling + +import com.typesafe.config.Config + +import org.opalj.fpcf.scheduling.CleanupCalculation.DisableCleanupKey +import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToKeepKey +import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToRemoveKey + +/** + * Class that allows to configure the cleanup of the PropertyStore inbetween phases programmatically + * @param keep IDs of the PropertyKeys to be kept at the end + * @param clear IDs of the PropertyKeys to be definitely removed + * @param disable Allows the cleanup to be disabled since it is on by default + */ +final case class CleanupSpec( + keep: Set[Int] = Set.empty, + clear: Set[Int] = Set.empty, + disable: Boolean = false +) + +object Cleanup { + + /** + * Creates a [[CleanupSpec]] reading the optionally set Values from a given config. + * @param config Config to be read from + * @return A new [[CleanupSpec]] + */ + def fromConfig(config: Config): CleanupSpec = { + def getSetForProperty(propertyKey: String): Set[Int] = { + Option( + config.getString(propertyKey) + ).toSeq.flatMap(_.split(",")).filter(_.nonEmpty).map(PropertyKey.idByName).toSet + } + val toKeep = getSetForProperty(PropertiesToKeepKey) + val toClear = getSetForProperty(PropertiesToRemoveKey) + val disable = config.getBoolean(DisableCleanupKey) + CleanupSpec(toKeep, toClear, disable) + } + + /** + * Calculates the properties to be safely removed inbetween phases. Returns an unmodified schedule if cleanup is disabled + */ + def withPerPhaseCleanup[A]( + schedule: List[PhaseConfiguration[A]], + ps: PropertyStore, + spec: CleanupSpec + ): List[PhaseConfiguration[A]] = { + if (spec.disable) return schedule + + val producedInAllPhases: Set[Int] = + schedule.iterator.flatMap(_.propertyKinds.propertyKindsComputedInThisPhase.map(_.id)).toSet + val neededLater = Array.fill[Set[Int]](schedule.size + 1)(Set.empty) + var index = schedule.size - 1 + var acc: Set[Int] = Set.empty + while (index >= 0) { + val used: Set[Int] = schedule(index).scheduled.iterator.flatMap(_.uses(ps).iterator).map(_.pk.id).toSet + acc = acc union used + neededLater(index) = acc + index -= 1 + } + + schedule.indices.iterator.map { index => + val producedHere = schedule(index) + val toDelete = ((producedInAllPhases -- neededLater(index)) -- spec.keep) union spec.clear + producedHere.copy(toDelete = toDelete) + }.toList + } + +} diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala index 250af136ac..8ac690693b 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala @@ -4,15 +4,12 @@ package fpcf package scheduling import scala.collection.mutable -import scala.collection.mutable.ListBuffer import com.typesafe.config.Config import org.opalj.collection.IntIterator import org.opalj.fpcf.AnalysisScenario.ConfigKeyPrefix -import org.opalj.fpcf.scheduling.CleanupCalculation.DisableCleanup -import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToKeepKey -import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToRemoveKey +import org.opalj.fpcf.scheduling.CleanupCalculation.DisableCleanupKey import org.opalj.fpcf.scheduling.MultiplePhaseScheduling.AnalysisScheduleLazyTransformerInMultiplePhasesKey import org.opalj.graphs.sccs import org.opalj.graphs.topologicalSort @@ -102,7 +99,7 @@ abstract class MultiplePhaseScheduling extends SchedulingStrategy { computePhase(ps, phaseAnalyses, remainingAnalyses) } - if (!config.getBoolean(DisableCleanup)) calculateDeletions(schedule, ps, config) else schedule + if (!config.getBoolean(DisableCleanupKey)) calculateDeletions(schedule, ps, config) else schedule } private def calculateDeletions[A]( @@ -110,52 +107,8 @@ abstract class MultiplePhaseScheduling extends SchedulingStrategy { ps: PropertyStore, config: Config ): List[PhaseConfiguration[A]] = { - def keyByName(name: String): SomePropertyKey = { - val max = PropertyKey.maxId - var i = 0 - while (i <= max) { - if (PropertyKey.name(i) == name) return PropertyKey.key(i) - i += 1 - } - throw new IllegalArgumentException(s"Unknown property name: $name") - } - def getSetForProperty(propertyKey: String): Set[Int] = { - Option( - config.getString(propertyKey) - ).toSeq.flatMap(_.split(",")).filter(_.nonEmpty).map(name => keyByName(name).id).toSet - } - val pkToKeep = getSetForProperty(PropertiesToKeepKey) - val pkToDelete = getSetForProperty(PropertiesToRemoveKey) - val properties: Set[Int] = - schedule.iterator.flatMap( - _.propertyKinds.propertyKindsComputedInThisPhase.map(_.id) - ).toSet - - val modifiedSchedule: ListBuffer[PhaseConfiguration[A]] = ListBuffer.from(schedule) - - var usedDependencies: Set[Int] = Set.empty - var alreadyDeleted: Set[Int] = Set.empty - val lastIndex = modifiedSchedule.length - 1 - var index = lastIndex - while (index >= 0) { - val elem = modifiedSchedule(index) - val currentSchedule = elem.scheduled - usedDependencies ++= currentSchedule.iterator.flatMap(_.uses(ps).iterator.map(_.pk.id)).toSet - usedDependencies ++= pkToKeep - val remaining: Set[Int] = { - if (index == lastIndex) { - (properties -- pkToKeep) -- alreadyDeleted - } else - (properties -- usedDependencies) -- alreadyDeleted - } - - alreadyDeleted ++= remaining - index -= 1 - } - modifiedSchedule(lastIndex) = modifiedSchedule(lastIndex).copy( - toDelete = (modifiedSchedule(lastIndex).toDelete ++ pkToDelete) -- pkToKeep - ) - modifiedSchedule.toList + val spec = Cleanup.fromConfig(config) + Cleanup.withPerPhaseCleanup(schedule, ps, spec) } /** @@ -230,5 +183,5 @@ object MaximumPhaseScheduling extends MultiplePhaseScheduling { object CleanupCalculation { final val PropertiesToKeepKey = s"${ConfigKeyPrefix}KeepPropertyKeys" final val PropertiesToRemoveKey = s"${ConfigKeyPrefix}ClearPropertyKeys" - final val DisableCleanup = s"${ConfigKeyPrefix}DisableCleanup" + final val DisableCleanupKey = s"${ConfigKeyPrefix}DisableCleanup" } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala index 09f405ff55..8c75135667 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala @@ -831,9 +831,7 @@ final class PKESequentialPropertyStore protected ( if (exception != null) throw exception; } - private def clearObsoletePropertyKinds(): Unit = { - currentPhaseToDelete.foreach { key => ps(key).clear() } - } + override protected def clearSlot(id: Int): Unit = ps(id).clear() def shutdown(): Unit = {} } From a7d16c46521ce4bde5127aa09004683e80a9f51c Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Sun, 5 Oct 2025 18:14:58 +0200 Subject: [PATCH 08/17] move config --- .../org/opalj/support/info/Immutability.scala | 6 +-- .../scala/org/opalj/support/info/Purity.scala | 6 +-- .../org/opalj/cli/ClearPropertyKeysArg.scala | 22 --------- .../org/opalj/cli/DisableCleanupArg.scala | 17 ------- .../org/opalj/cli/KeepPropertyKeysArg.scala | 22 --------- .../scala/org/opalj/fpcf/PropertyKey.scala | 5 +- .../PropertyStoreBasedCommandLineConfig.scala | 48 +++++++++++++++++++ .../main/scala/org/opalj/fpcf/Schedule.scala | 11 ++++- .../opalj/fpcf/par/PKECPropertyStore.scala | 1 - .../scheduling/MaximumPhaseScheduling.scala | 13 +---- 10 files changed, 68 insertions(+), 83 deletions(-) delete mode 100644 OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala delete mode 100644 OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala delete mode 100644 OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala index 1c632f2c40..d6c9c002eb 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala @@ -56,21 +56,21 @@ import org.opalj.bytecode.JDKArg import org.opalj.bytecode.JRELibraryFolder import org.opalj.cli.AnalysisLevelArg import org.opalj.cli.ChoiceArg -import org.opalj.cli.ClearPropertyKeysArg import org.opalj.cli.ConfigurationNameArg -import org.opalj.cli.DisableCleanupArg -import org.opalj.cli.KeepPropertyKeysArg import org.opalj.cli.LibraryArg import org.opalj.cli.OutputDirArg import org.opalj.cli.ParsedArg import org.opalj.cli.PlainArg import org.opalj.cli.ThreadsNumArg +import org.opalj.fpcf.ClearPropertyKeysArg import org.opalj.fpcf.ComputationSpecification +import org.opalj.fpcf.DisableCleanupArg import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS import org.opalj.fpcf.FPCFAnalysesManagerKey import org.opalj.fpcf.FPCFAnalysis import org.opalj.fpcf.FPCFAnalysisScheduler +import org.opalj.fpcf.KeepPropertyKeysArg import org.opalj.fpcf.OrderedProperty import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala index b462c2930c..f65f1754b0 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala @@ -42,19 +42,19 @@ import org.opalj.br.fpcf.properties.SideEffectFree import org.opalj.bytecode.JDKArg import org.opalj.bytecode.JDKPackages import org.opalj.cli.AnalysisLevelArg -import org.opalj.cli.ClearPropertyKeysArg import org.opalj.cli.ConfigurationNameArg -import org.opalj.cli.DisableCleanupArg import org.opalj.cli.EagerArg import org.opalj.cli.IndividualArg -import org.opalj.cli.KeepPropertyKeysArg import org.opalj.cli.OutputDirArg import org.opalj.cli.PackagesArg import org.opalj.collection.immutable.IntTrieSet +import org.opalj.fpcf.ClearPropertyKeysArg import org.opalj.fpcf.ComputationSpecification +import org.opalj.fpcf.DisableCleanupArg import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.FPCFAnalysesManagerKey +import org.opalj.fpcf.KeepPropertyKeysArg import org.opalj.tac.cg.CallGraphArg import org.opalj.tac.cg.CGBasedCommandLineConfig import org.opalj.tac.fpcf.analyses.LazyFieldImmutabilityAnalysis diff --git a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala deleted file mode 100644 index a324094af8..0000000000 --- a/OPAL/common/src/main/scala/org/opalj/cli/ClearPropertyKeysArg.scala +++ /dev/null @@ -1,22 +0,0 @@ -package org.opalj -package cli - -import com.typesafe.config.Config -import com.typesafe.config.ConfigValueFactory - -object ClearPropertyKeysArg extends ParsedArg[List[String], List[String]] { - override val name: String = "clearPropertyKeys" - override val description: String = "List of Properties to keep at the end of the analysis" - override val defaultValue: Option[List[String]] = None - - override def apply(config: Config, value: Option[List[String]]): Config = { - config.withValue( - "org.opalj.fpcf.AnalysisScenario.ClearPropertyKeys", - ConfigValueFactory.fromAnyRef(value.getOrElse("")) - ) - } - - override def parse(arg: List[String]): List[String] = { - arg.flatMap(_.split(",")) - } -} diff --git a/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala deleted file mode 100644 index 9902641684..0000000000 --- a/OPAL/common/src/main/scala/org/opalj/cli/DisableCleanupArg.scala +++ /dev/null @@ -1,17 +0,0 @@ -package org.opalj -package cli - -import com.typesafe.config.Config -import com.typesafe.config.ConfigValueFactory - -object DisableCleanupArg extends PlainArg[Boolean] { - override val name: String = "disableCleanup" - override def description: String = "Disable cleanup of the PropertyStore inbetween phases" - override val defaultValue: Option[Boolean] = Some(false) - override def apply(config: Config, value: Option[Boolean]): Config = { - config.withValue( - "org.opalj.fpcf.AnalysisScenario.DisableCleanup", - ConfigValueFactory.fromAnyRef(value.getOrElse(false)) - ) - } -} diff --git a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala b/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala deleted file mode 100644 index 158b57d149..0000000000 --- a/OPAL/common/src/main/scala/org/opalj/cli/KeepPropertyKeysArg.scala +++ /dev/null @@ -1,22 +0,0 @@ -package org.opalj -package cli - -import com.typesafe.config.Config -import com.typesafe.config.ConfigValueFactory - -object KeepPropertyKeysArg extends ParsedArg[List[String], List[String]] { - override val name: String = "keepPropertyKeys" - override val description: String = "List of Properties to keep at the end of the analysis" - override val defaultValue: Option[List[String]] = None - - override def apply(config: Config, value: Option[List[String]]): Config = { - config.withValue( - "org.opalj.fpcf.AnalysisScenario.KeepPropertyKeys", - ConfigValueFactory.fromAnyRef(value.getOrElse("")) - ) - } - - override def parse(arg: List[String]): List[String] = { - arg.flatMap(_.split(",")) - } -} diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala index 5c5213d84b..fb9ea9416c 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala @@ -128,7 +128,10 @@ object PropertyKey { def name(id: Int): String = propertyKeyNames(id) def idByName(name: String): Int = - byName.getOrElse(name, throw new IllegalArgumentException(s"Unknown property name: $name")).id + getByName(name).id + + def getByName(name: String): SomePropertyKey = + byName.getOrElse(name, throw new IllegalArgumentException(s"Unknown property name: $name")) final def name(pKind: PropertyKind): String = name(pKind.id) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala index 592cd2b0b6..9c3f070c3f 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala @@ -3,6 +3,7 @@ package org.opalj package fpcf import com.typesafe.config.Config +import com.typesafe.config.ConfigValueFactory import org.rogach.scallop.ScallopConf import org.rogach.scallop.flagConverter @@ -12,6 +13,7 @@ import org.opalj.cli.Arg import org.opalj.cli.ConvertedArg import org.opalj.cli.ForwardingArg import org.opalj.cli.OPALCommandLineConfig +import org.opalj.cli.ParsedArg import org.opalj.cli.PlainArg import org.opalj.fpcf.par.SchedulingStrategyArg import org.opalj.log.LogContext @@ -91,3 +93,49 @@ trait PropertyStoreBasedCommandLineConfig extends OPALCommandLineConfig { self: else FPCFAnalysesRegistry.lazyFactory(analysisName) } } + +object DisableCleanupArg extends PlainArg[Boolean] { + override val name: String = "disableCleanup" + override def description: String = "Disable cleanup of the PropertyStore inbetween phases" + override val defaultValue: Option[Boolean] = Some(false) + override def apply(config: Config, value: Option[Boolean]): Config = { + config.withValue( + "org.opalj.fpcf.AnalysisScenario.DisableCleanup", + ConfigValueFactory.fromAnyRef(value.getOrElse(false)) + ) + } +} + +object KeepPropertyKeysArg extends ParsedArg[List[String], List[SomePropertyKey]] { + override val name: String = "keepPropertyKeys" + override val description: String = "List of Properties to keep at the end of the analysis" + override val defaultValue: Option[List[String]] = None + + override def apply(config: Config, value: Option[List[SomePropertyKey]]): Config = { + config.withValue( + "org.opalj.fpcf.AnalysisScenario.KeepPropertyKeys", + ConfigValueFactory.fromAnyRef(value.getOrElse("")) + ) + } + + override def parse(arg: List[String]): List[SomePropertyKey] = { + arg.flatMap(_.split(",")).map(PropertyKey.getByName) + } +} + +object ClearPropertyKeysArg extends ParsedArg[List[String], List[SomePropertyKey]] { + override val name: String = "clearPropertyKeys" + override val description: String = "List of Properties to keep at the end of the analysis" + override val defaultValue: Option[List[String]] = None + + override def apply(config: Config, value: Option[List[SomePropertyKey]]): Config = { + config.withValue( + "org.opalj.fpcf.AnalysisScenario.ClearPropertyKeys", + ConfigValueFactory.fromAnyRef(value.getOrElse("")) + ) + } + + override def parse(arg: List[String]): List[SomePropertyKey] = { + arg.flatMap(_.split(",")).map(PropertyKey.getByName) + } +} diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index fe80c2ab97..b621f8dc74 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -43,9 +43,14 @@ case class Schedule[A]( ): List[(ComputationSpecification[A], A)] = { implicit val logContext: LogContext = ps.logContext + val config = ps.context(classOf[com.typesafe.config.Config]) + val cleanupSpec = scheduling.Cleanup.fromConfig(config) + val phases = + if (cleanupSpec.disable) batches else scheduling.Cleanup.withPerPhaseCleanup(batches, ps, cleanupSpec) + var allExecutedAnalyses: List[(ComputationSpecification[A], A)] = Nil - batches.iterator.zipWithIndex foreach { batchId => + phases.iterator.zipWithIndex foreach { batchId => val (phase, id) = batchId val configuration = phase.propertyKinds val css = phase.scheduled @@ -95,8 +100,10 @@ case class Schedule[A]( ) } } + val finalToDelete: Set[Int] = if (cleanupSpec.disable) Set.empty else cleanupSpec.clear -- cleanupSpec.keep + // ... we are done now; the computed properties will no longer be computed! - ps.setupPhase(Set.empty, Set.empty) + ps.setupPhase(Set.empty, Set.empty, toDelete = finalToDelete) allExecutedAnalyses } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala index fb24910076..2b23633e6c 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -17,7 +17,6 @@ import com.typesafe.config.Config import org.opalj.control.foreachWithIndex import org.opalj.fpcf.PropertyKey.fallbackPropertyBasedOnPKId import org.opalj.log.LogContext -import org.opalj.util.PerformanceEvaluation.memory /** * Yet another parallel property store. diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala index 8ac690693b..8ec33e234e 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/MaximumPhaseScheduling.scala @@ -9,7 +9,6 @@ import com.typesafe.config.Config import org.opalj.collection.IntIterator import org.opalj.fpcf.AnalysisScenario.ConfigKeyPrefix -import org.opalj.fpcf.scheduling.CleanupCalculation.DisableCleanupKey import org.opalj.fpcf.scheduling.MultiplePhaseScheduling.AnalysisScheduleLazyTransformerInMultiplePhasesKey import org.opalj.graphs.sccs import org.opalj.graphs.topologicalSort @@ -98,17 +97,7 @@ abstract class MultiplePhaseScheduling extends SchedulingStrategy { remainingAnalyses --= phaseAnalyses computePhase(ps, phaseAnalyses, remainingAnalyses) } - - if (!config.getBoolean(DisableCleanupKey)) calculateDeletions(schedule, ps, config) else schedule - } - - private def calculateDeletions[A]( - schedule: List[PhaseConfiguration[A]], - ps: PropertyStore, - config: Config - ): List[PhaseConfiguration[A]] = { - val spec = Cleanup.fromConfig(config) - Cleanup.withPerPhaseCleanup(schedule, ps, spec) + schedule } /** From d9840430870f563880dd43ce3fc51741f20b6ac2 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Mon, 6 Oct 2025 18:27:04 +0200 Subject: [PATCH 09/17] add scaladoc, rename variables --- .../org/opalj/fpcf/AnalysisScenario.scala | 3 +- .../scala/org/opalj/fpcf/PropertyKey.scala | 14 ++++---- .../scala/org/opalj/fpcf/PropertyStore.scala | 15 +++++--- .../main/scala/org/opalj/fpcf/Schedule.scala | 14 ++++---- .../opalj/fpcf/par/PKECPropertyStore.scala | 2 +- .../org/opalj/fpcf/scheduling/Cleanup.scala | 34 ++++++------------- .../fpcf/seq/PKESequentialPropertyStore.scala | 2 +- 7 files changed, 42 insertions(+), 42 deletions(-) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala index c52d1c2e69..4236cc738d 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala @@ -294,7 +294,8 @@ class AnalysisScenario[A](val ps: PropertyStore) { Schedule( scheduledBatches, - initializationData + initializationData, + None ) } } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala index fb9ea9416c..93bf94a448 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala @@ -111,10 +111,6 @@ object PropertyKey { create(name, fpc) } - lazy val byName: Map[String, SomePropertyKey] = { - propertyKeys.iterator.map { k => PropertyKey.name(k.id) -> k }.toMap - } - // // Query the core properties of each property kind // =============================================== @@ -130,8 +126,14 @@ object PropertyKey { def idByName(name: String): Int = getByName(name).id - def getByName(name: String): SomePropertyKey = - byName.getOrElse(name, throw new IllegalArgumentException(s"Unknown property name: $name")) + /** + * Returns a [[SomePropertyKey]] associated with the given name. To get it by id use [[key]]. Throws an [[IllegalArgumentException]] if no [[PropertyKey]] exists for the given name. + */ + def getByName(name: String): SomePropertyKey = { + propertyKeys.find(k => PropertyKey.name(k.id) == name).getOrElse( + throw new IllegalArgumentException(s"Unknown property name: $name") + ) + } final def name(pKind: PropertyKind): String = name(pKind.id) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala index 45663e0a5d..39c90dae42 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala @@ -500,18 +500,25 @@ abstract class PropertyStore { protected[this] def doSet(e: Entity, p: Property): Unit + /** + * Removes [[PropertyKind]] from a [[PropertyStore]] using a pre-calculated [[currentPhaseDeletionMask]]. Calls [[clearPK]] which need to be implemented in the implementations of the [[PropertyStore]]. + */ protected[fpcf] final def clearObsoletePropertyKinds(): Unit = { val mask = currentPhaseDeletionMask var index = 0 while (index < mask.length) { if (mask(index)) { - clearSlot(index) + clearPK(index) } index += 1 } } - protected def clearSlot(id: Int): Unit + /** + * Placeholder to remove a given [[PropertyKey]] from a [[PropertyStore]]. Needs to be overriden in the implementation for access to the given [[PropertyStore]]. + * @param id ID of the [[PropertyKey]] to be removed + */ + protected def clearPK(id: Int): Unit /** * Associates the given entity with the newly computed intermediate property P. @@ -543,13 +550,13 @@ abstract class PropertyStore { pc: EOptionP[E, P] => InterimEP[E, P] ): Unit - final def setupPhase(configuration: PropertyKindsConfiguration): Unit = { + final def setupPhase(configuration: PropertyKindsConfiguration, toDelete: Set[Int]): Unit = { setupPhase( configuration.propertyKindsComputedInThisPhase, configuration.propertyKindsComputedInLaterPhase, configuration.suppressInterimUpdates, configuration.collaborativelyComputedPropertyKindsFinalizationOrder, - toDelete = Set.empty + toDelete ) } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index b621f8dc74..959b2abe2a 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -2,6 +2,7 @@ package org.opalj package fpcf +import org.opalj.fpcf.scheduling.CleanupSpec import org.opalj.log.LogContext import org.opalj.log.OPALLogger.info import org.opalj.util.PerformanceEvaluation.time @@ -17,7 +18,8 @@ import org.opalj.util.PerformanceEvaluation.time */ case class Schedule[A]( batches: List[PhaseConfiguration[A]], - initializationData: Map[ComputationSpecification[A], Any] + initializationData: Map[ComputationSpecification[A], Any], + cleanupSpec: Option[CleanupSpec] ) extends ( ( PropertyStore, @@ -43,10 +45,7 @@ case class Schedule[A]( ): List[(ComputationSpecification[A], A)] = { implicit val logContext: LogContext = ps.logContext - val config = ps.context(classOf[com.typesafe.config.Config]) - val cleanupSpec = scheduling.Cleanup.fromConfig(config) - val phases = - if (cleanupSpec.disable) batches else scheduling.Cleanup.withPerPhaseCleanup(batches, ps, cleanupSpec) + val phases = cleanupSpec.map(scheduling.Cleanup.withPerPhaseCleanup(batches, ps, _)).getOrElse(batches) var allExecutedAnalyses: List[(ComputationSpecification[A], A)] = Nil @@ -100,7 +99,10 @@ case class Schedule[A]( ) } } - val finalToDelete: Set[Int] = if (cleanupSpec.disable) Set.empty else cleanupSpec.clear -- cleanupSpec.keep + val finalToDelete: Set[Int] = cleanupSpec match { + case Some(spec) if !spec.disable => spec.clear -- spec.keep + case _ => Set.empty + } // ... we are done now; the computed properties will no longer be computed! ps.setupPhase(Set.empty, Set.empty, toDelete = finalToDelete) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala index 2b23633e6c..3ae02bd92b 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/par/PKECPropertyStore.scala @@ -526,7 +526,7 @@ class PKECPropertyStore( idle = true } - override protected def clearSlot(id: Int): Unit = ps(id).clear() + override protected def clearPK(id: Int): Unit = ps(id).clear() private[this] val interimStates: Array[ArrayBuffer[EPKState]] = Array.fill(THREAD_COUNT)(null) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala index 15189102f4..4e781b4366 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala @@ -2,12 +2,6 @@ package org.opalj package fpcf package scheduling -import com.typesafe.config.Config - -import org.opalj.fpcf.scheduling.CleanupCalculation.DisableCleanupKey -import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToKeepKey -import org.opalj.fpcf.scheduling.CleanupCalculation.PropertiesToRemoveKey - /** * Class that allows to configure the cleanup of the PropertyStore inbetween phases programmatically * @param keep IDs of the PropertyKeys to be kept at the end @@ -23,19 +17,12 @@ final case class CleanupSpec( object Cleanup { /** - * Creates a [[CleanupSpec]] reading the optionally set Values from a given config. - * @param config Config to be read from + * Creates a [[CleanupSpec]] from given [[PropertyKey]]-names to keep and/or to clear. * @return A new [[CleanupSpec]] */ - def fromConfig(config: Config): CleanupSpec = { - def getSetForProperty(propertyKey: String): Set[Int] = { - Option( - config.getString(propertyKey) - ).toSeq.flatMap(_.split(",")).filter(_.nonEmpty).map(PropertyKey.idByName).toSet - } - val toKeep = getSetForProperty(PropertiesToKeepKey) - val toClear = getSetForProperty(PropertiesToRemoveKey) - val disable = config.getBoolean(DisableCleanupKey) + def fromArgs(keep: Set[String], clear: Set[String], disable: Boolean): CleanupSpec = { + val toKeep = keep.map(PropertyKey.idByName) + val toClear = clear.map(PropertyKey.idByName) CleanupSpec(toKeep, toClear, disable) } @@ -49,21 +36,22 @@ object Cleanup { ): List[PhaseConfiguration[A]] = { if (spec.disable) return schedule - val producedInAllPhases: Set[Int] = + val producedInAnyPhase: Set[Int] = schedule.iterator.flatMap(_.propertyKinds.propertyKindsComputedInThisPhase.map(_.id)).toSet val neededLater = Array.fill[Set[Int]](schedule.size + 1)(Set.empty) var index = schedule.size - 1 - var acc: Set[Int] = Set.empty + var usedInAnyPhase: Set[Int] = Set.empty while (index >= 0) { - val used: Set[Int] = schedule(index).scheduled.iterator.flatMap(_.uses(ps).iterator).map(_.pk.id).toSet - acc = acc union used - neededLater(index) = acc + val usedInThisPhase: Set[Int] = + schedule(index).scheduled.iterator.flatMap(_.uses(ps).iterator).map(_.pk.id).toSet + usedInAnyPhase = usedInAnyPhase union usedInThisPhase + neededLater(index) = usedInAnyPhase index -= 1 } schedule.indices.iterator.map { index => val producedHere = schedule(index) - val toDelete = ((producedInAllPhases -- neededLater(index)) -- spec.keep) union spec.clear + val toDelete = ((producedInAnyPhase -- neededLater(index)) -- spec.keep) union spec.clear producedHere.copy(toDelete = toDelete) }.toList } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala index 8c75135667..e9fa96f9ac 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/seq/PKESequentialPropertyStore.scala @@ -831,7 +831,7 @@ final class PKESequentialPropertyStore protected ( if (exception != null) throw exception; } - override protected def clearSlot(id: Int): Unit = ps(id).clear() + override protected def clearPK(id: Int): Unit = ps(id).clear() def shutdown(): Unit = {} } From 4af591c14c249bb4ef417b2c322608495e94582f Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Mon, 6 Oct 2025 18:42:51 +0200 Subject: [PATCH 10/17] add to cleanup-docu --- .../src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala index 4e781b4366..1c9e8c8506 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala @@ -17,7 +17,10 @@ final case class CleanupSpec( object Cleanup { /** - * Creates a [[CleanupSpec]] from given [[PropertyKey]]-names to keep and/or to clear. + * Creates a [[CleanupSpec]] from given [[PropertyKey]]-names to keep and/or to clear. Also allows disabling the cleanup by setting 'disable' to 'true'. + * @param keep Names of PropertyKeys to be kept after the analyses + * @param clear Names of PropertyKeys to be removed after the analyses + * @param disable Setting this to 'true' disables the cleanup inbetween the phases * @return A new [[CleanupSpec]] */ def fromArgs(keep: Set[String], clear: Set[String], disable: Boolean): CleanupSpec = { From df3018f714f4b46dfd46ce121728d9b887104c03 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Tue, 7 Oct 2025 12:54:24 +0200 Subject: [PATCH 11/17] add to cleanup-docu --- OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala index 1c9e8c8506..13602ee7fd 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala @@ -14,6 +14,9 @@ final case class CleanupSpec( disable: Boolean = false ) +/** + * Factory for creating [[CleanupSpec]]-objects, also handles calculation of per-phase cleanup + */ object Cleanup { /** From 5dd519e903ede6507f05d6da2721fd591c6bff2d Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Wed, 8 Oct 2025 13:03:07 +0200 Subject: [PATCH 12/17] add license-header --- OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala index 13602ee7fd..8f8201a472 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala @@ -1,3 +1,4 @@ +/* BSD 2-Clause License - see OPAL/LICENSE for details. */ package org.opalj package fpcf package scheduling From ea4c15a37f99aa4be05b0736d577dbd6b4f51329 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Wed, 8 Oct 2025 13:49:29 +0200 Subject: [PATCH 13/17] move params --- .../main/scala/org/opalj/support/info/Immutability.scala | 5 +---- .../src/main/scala/org/opalj/support/info/Purity.scala | 5 +---- OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala | 9 ++------- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala index d6c9c002eb..44140232b8 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala @@ -135,10 +135,7 @@ object Immutability extends ProjectsAnalysisApplication { analysisLevelArg !, ignoreLazyInitializationArg !, ConfigurationNameArg !, - EscapeArg, - DisableCleanupArg, - KeepPropertyKeysArg, - ClearPropertyKeysArg + EscapeArg ) init() diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala index f65f1754b0..332e11e00b 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala @@ -97,10 +97,7 @@ object Purity extends ProjectsAnalysisApplication { RaterArg !, IndividualArg, OutputDirArg, - PackagesArg, - DisableCleanupArg, - KeepPropertyKeysArg, - ClearPropertyKeysArg + PackagesArg ) generalArgs( DomainArg diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index 959b2abe2a..495bb43412 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -58,13 +58,8 @@ case class Schedule[A]( info("analysis progress", s"setting up analysis phase $id: $configuration") } time { - ps.setupPhase( - configuration.propertyKindsComputedInThisPhase, - configuration.propertyKindsComputedInLaterPhase, - configuration.suppressInterimUpdates, - configuration.collaborativelyComputedPropertyKindsFinalizationOrder, - phase.toDelete - ) + ps.setupPhase(configuration, phase.toDelete) + afterPhaseSetup(configuration) assert(ps.isIdle, "the property store is not idle after phase setup") From 5dbdb460470e606af2a4871c80309bd28e2305a1 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Wed, 8 Oct 2025 14:11:48 +0200 Subject: [PATCH 14/17] add new step --- .../scala/org/opalj/support/info/Immutability.scala | 3 --- .../main/scala/org/opalj/support/info/Purity.scala | 3 --- .../main/scala/org/opalj/fpcf/PropertyStore.scala | 12 +++++++----- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala index 44140232b8..0f7dfc02ef 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Immutability.scala @@ -62,15 +62,12 @@ import org.opalj.cli.OutputDirArg import org.opalj.cli.ParsedArg import org.opalj.cli.PlainArg import org.opalj.cli.ThreadsNumArg -import org.opalj.fpcf.ClearPropertyKeysArg import org.opalj.fpcf.ComputationSpecification -import org.opalj.fpcf.DisableCleanupArg import org.opalj.fpcf.Entity import org.opalj.fpcf.EPS import org.opalj.fpcf.FPCFAnalysesManagerKey import org.opalj.fpcf.FPCFAnalysis import org.opalj.fpcf.FPCFAnalysisScheduler -import org.opalj.fpcf.KeepPropertyKeysArg import org.opalj.fpcf.OrderedProperty import org.opalj.fpcf.Property import org.opalj.fpcf.PropertyKey diff --git a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala index 332e11e00b..cde09842c7 100644 --- a/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala +++ b/DEVELOPING_OPAL/tools/src/main/scala/org/opalj/support/info/Purity.scala @@ -48,13 +48,10 @@ import org.opalj.cli.IndividualArg import org.opalj.cli.OutputDirArg import org.opalj.cli.PackagesArg import org.opalj.collection.immutable.IntTrieSet -import org.opalj.fpcf.ClearPropertyKeysArg import org.opalj.fpcf.ComputationSpecification -import org.opalj.fpcf.DisableCleanupArg import org.opalj.fpcf.FinalEP import org.opalj.fpcf.FinalP import org.opalj.fpcf.FPCFAnalysesManagerKey -import org.opalj.fpcf.KeepPropertyKeysArg import org.opalj.tac.cg.CallGraphArg import org.opalj.tac.cg.CGBasedCommandLineConfig import org.opalj.tac.fpcf.analyses.LazyFieldImmutabilityAnalysis diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala index 39c90dae42..51e8f504e0 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala @@ -607,11 +607,6 @@ abstract class PropertyStore { "illegal self dependency" ) - val mask = currentPhaseDeletionMask - java.util.Arrays.fill(mask, false) - - toDelete.foreach(id => mask(id) = true) - // Step 1 // Copy all property kinds that were computed in the previous phase that are no // longer computed to the "propertyKindsComputedInEarlierPhase" array. @@ -674,6 +669,13 @@ abstract class PropertyStore { suppressInterimUpdates, finalizationOrder ) + + // Step 6 + // Set up a new deletionMask by resetting it first, then filling it with the help of toDelete. + val mask = currentPhaseDeletionMask + java.util.Arrays.fill(mask, false) + + toDelete.foreach(id => mask(id) = true) } /** From da6dcbf565e88b76338a7cc93d15796a1c3b24cb Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Wed, 8 Oct 2025 23:06:12 +0200 Subject: [PATCH 15/17] add new step in docu, fix logical flaw in calculation, enable cleanup per default for now --- .../src/main/scala/org/opalj/fpcf/AnalysisScenario.scala | 3 ++- .../si/src/main/scala/org/opalj/fpcf/PropertyStore.scala | 9 +++++++++ .../opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala | 5 ++++- OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala | 3 +++ .../main/scala/org/opalj/fpcf/scheduling/Cleanup.scala | 2 +- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala index 4236cc738d..59257af202 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala @@ -6,6 +6,7 @@ import com.typesafe.config.Config import org.opalj.fpcf.AnalysisScenario.AnalysisAutoConfigKey import org.opalj.fpcf.AnalysisScenario.AnalysisSchedulingStrategyKey +import org.opalj.fpcf.scheduling.CleanupSpec import org.opalj.fpcf.scheduling.SchedulingStrategy import org.opalj.graphs.Graph import org.opalj.log.LogContext @@ -295,7 +296,7 @@ class AnalysisScenario[A](val ps: PropertyStore) { Schedule( scheduledBatches, initializationData, - None + Some(CleanupSpec()) ) } } diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala index 51e8f504e0..7a2f2f3455 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala @@ -550,6 +550,15 @@ abstract class PropertyStore { pc: EOptionP[E, P] => InterimEP[E, P] ): Unit + final def setupPhase(configuration: PropertyKindsConfiguration): Unit = { + setupPhase( + configuration.propertyKindsComputedInThisPhase, + configuration.propertyKindsComputedInLaterPhase, + configuration.suppressInterimUpdates, + configuration.collaborativelyComputedPropertyKindsFinalizationOrder + ) + } + final def setupPhase(configuration: PropertyKindsConfiguration, toDelete: Set[Int]): Unit = { setupPhase( configuration.propertyKindsComputedInThisPhase, diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala index 9c3f070c3f..e0bad17cea 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStoreBasedCommandLineConfig.scala @@ -70,7 +70,10 @@ trait PropertyStoreBasedCommandLineConfig extends OPALCommandLineConfig { self: generalArgs( PropertyStoreThreadsNumArg, PropertyStoreDebugArg, - SchedulingStrategyArg + SchedulingStrategyArg, + DisableCleanupArg, + KeepPropertyKeysArg, + ClearPropertyKeysArg ) def setupPropertyStore(project: Project): (PropertyStore, Seconds) = { diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index 495bb43412..72eefa4f43 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -56,6 +56,9 @@ case class Schedule[A]( if (trace) { info("analysis progress", s"setting up analysis phase $id: $configuration") + if (phase.toDelete.nonEmpty) { + info("cleanup", s"to be deleted after this phase: " + phase.toDelete.map(PropertyKey.name)) + } } time { ps.setupPhase(configuration, phase.toDelete) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala index 8f8201a472..a71e4ac9e7 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala @@ -51,8 +51,8 @@ object Cleanup { while (index >= 0) { val usedInThisPhase: Set[Int] = schedule(index).scheduled.iterator.flatMap(_.uses(ps).iterator).map(_.pk.id).toSet - usedInAnyPhase = usedInAnyPhase union usedInThisPhase neededLater(index) = usedInAnyPhase + usedInAnyPhase = usedInAnyPhase union usedInThisPhase index -= 1 } From 91b3a6a7ecbfff7ed7b3694ab16f6ba2853a00e0 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Thu, 9 Oct 2025 14:59:52 +0200 Subject: [PATCH 16/17] remove unneccessary toDelete-calculation for final phase --- OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index 72eefa4f43..811894c83b 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -97,13 +97,9 @@ case class Schedule[A]( ) } } - val finalToDelete: Set[Int] = cleanupSpec match { - case Some(spec) if !spec.disable => spec.clear -- spec.keep - case _ => Set.empty - } // ... we are done now; the computed properties will no longer be computed! - ps.setupPhase(Set.empty, Set.empty, toDelete = finalToDelete) + ps.setupPhase(Set.empty, propertyKindsComputedInLaterPhase = Set.empty) allExecutedAnalyses } From 8220f87c3fb56624ffe2302df768c4a3c029d929 Mon Sep 17 00:00:00 2001 From: HerrdesOlymp Date: Thu, 9 Oct 2025 15:25:46 +0200 Subject: [PATCH 17/17] change key for deletion-logging, fix order for steps --- .../scala/org/opalj/fpcf/PropertyStore.scala | 19 +++++++++++-------- .../main/scala/org/opalj/fpcf/Schedule.scala | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala index 7a2f2f3455..dc46895d0e 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala @@ -670,21 +670,23 @@ abstract class PropertyStore { hasSuppressedNotifications = suppressInterimUpdates.nonEmpty // Step 5 + // Set up a new deletionMask by resetting it first, then filling it with the help of toDelete. + val mask = currentPhaseDeletionMask + java.util.Arrays.fill(mask, false) + + toDelete.foreach(id => mask(id) = true) + + // Step 6 // Call `newPhaseInitialized` to enable subclasses to perform custom initialization steps // when a phase was setup. newPhaseInitialized( propertyKindsComputedInThisPhase, propertyKindsComputedInLaterPhase, suppressInterimUpdates, - finalizationOrder + finalizationOrder, + currentPhaseDeletionMask ) - // Step 6 - // Set up a new deletionMask by resetting it first, then filling it with the help of toDelete. - val mask = currentPhaseDeletionMask - java.util.Arrays.fill(mask, false) - - toDelete.foreach(id => mask(id) = true) } /** @@ -695,7 +697,8 @@ abstract class PropertyStore { propertyKindsComputedInThisPhase: Set[PropertyKind], propertyKindsComputedInLaterPhase: Set[PropertyKind], suppressInterimUpdates: Map[PropertyKind, Set[PropertyKind]], - finalizationOrder: List[List[PropertyKind]] + finalizationOrder: List[List[PropertyKind]], + phaseDeletionMask: Array[Boolean] ): Unit = { /*nothing to do*/ } /** diff --git a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala index 811894c83b..d1ecda9995 100644 --- a/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala +++ b/OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala @@ -57,7 +57,7 @@ case class Schedule[A]( if (trace) { info("analysis progress", s"setting up analysis phase $id: $configuration") if (phase.toDelete.nonEmpty) { - info("cleanup", s"to be deleted after this phase: " + phase.toDelete.map(PropertyKey.name)) + info("analysis progress", s"to be deleted after this phase: " + phase.toDelete.map(PropertyKey.name)) } } time {