Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
079ec9e
Add cleaning to the PropertyStore
fwnter Sep 18, 2025
e19a5b7
Fix Nullpointer, streamline code
fwnter Sep 18, 2025
7de135a
incooperate feedback, revert some changes
fwnter Sep 19, 2025
8d9148c
Merge branch 'opalj:develop' into develop
fwnter Sep 28, 2025
0c6a47a
Merge branch 'opalj:develop' into develop
fwnter Oct 1, 2025
e1c4510
fixes, no longer expect ids in parameters
fwnter Oct 2, 2025
08c12af
Merge remote-tracking branch 'origin/develop' into develop
fwnter Oct 2, 2025
180be94
add cleanup to PKESequentialPropertyStore
fwnter Oct 2, 2025
885d23f
formatting
fwnter Oct 2, 2025
ef9b83f
add programmatical way to setup cleanup
fwnter Oct 4, 2025
a7d16c4
move config
fwnter Oct 5, 2025
d984043
add scaladoc, rename variables
fwnter Oct 6, 2025
4af591c
add to cleanup-docu
fwnter Oct 6, 2025
df3018f
add to cleanup-docu
fwnter Oct 7, 2025
5dd519e
add license-header
fwnter Oct 8, 2025
3b0737b
Merge branch 'develop' into develop
fwnter Oct 8, 2025
ea4c15a
move params
fwnter Oct 8, 2025
645bb56
Merge remote-tracking branch 'origin/develop' into develop
fwnter Oct 8, 2025
5dbdb46
add new step
fwnter Oct 8, 2025
da6dcbf
add new step in docu, fix logical flaw in calculation, enable cleanup…
fwnter Oct 8, 2025
7f671d1
Merge branch 'opalj:develop' into develop
fwnter Oct 8, 2025
91b3a6a
remove unneccessary toDelete-calculation for final phase
fwnter Oct 9, 2025
8220f87
change key for deletion-logging, fix order for steps
fwnter Oct 9, 2025
5a8d0a3
Merge branch 'develop' into develop
fwnter Dec 21, 2025
cdbfd83
add interface
fwnter Jan 5, 2026
0c13eb1
incooperate feedback, add docu
fwnter Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -209,7 +210,8 @@ class AnalysisScenario[A](val ps: PropertyStore) {
*/
def computeSchedule(
propertyStore: PropertyStore,
defaultAnalysis: PropertyBounds => Option[ComputationSpecification[A]] = _ => None
defaultAnalysis: PropertyBounds => Option[ComputationSpecification[A]] = _ => None,
cleanupSpec: Option[CleanupSpec] = None
)(
implicit logContext: LogContext
): Schedule[A] = {
Expand Down Expand Up @@ -282,7 +284,8 @@ class AnalysisScenario[A](val ps: PropertyStore) {

Schedule(
scheduledBatches,
initializationData
initializationData,
cleanupSpec
)
}
}
Expand Down
11 changes: 7 additions & 4 deletions OPAL/si/src/main/scala/org/opalj/fpcf/FPCFAnalysesManager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package fpcf

import com.typesafe.config.Config

import org.opalj.fpcf.scheduling.CleanupSpec
import org.opalj.log.LogContext
import org.opalj.log.OPALLogger.debug
import org.opalj.si.Project
Expand Down Expand Up @@ -33,20 +34,22 @@ class FPCFAnalysesManager private[fpcf] (val project: Project) {
def executedSchedules: List[Schedule[FPCFAnalysis]] = schedules

final def runAll[T <: FPCFAnalysis](
analyses: ComputationSpecification[T]*
cleanupSpec: CleanupSpec,
analyses: ComputationSpecification[T]*
): (PropertyStore, List[(ComputationSpecification[FPCFAnalysis], FPCFAnalysis)]) = {
runAll(analyses.to(Iterable))
runAll(analyses.to(Iterable), cleanupSpec = Some(cleanupSpec))
}

final def runAll[T <: FPCFAnalysis](
analyses: Iterable[ComputationSpecification[T]],
afterPhaseScheduling: List[ComputationSpecification[FPCFAnalysis]] => Unit = _ => ()
afterPhaseScheduling: List[ComputationSpecification[FPCFAnalysis]] => Unit = _ => (),
cleanupSpec: Option[CleanupSpec] = None
): (PropertyStore, List[(ComputationSpecification[FPCFAnalysis], FPCFAnalysis)]) = this.synchronized {

val scenario =
AnalysisScenario(analyses.asInstanceOf[Iterable[ComputationSpecification[FPCFAnalysis]]], propertyStore)

val schedule = scenario.computeSchedule(propertyStore, FPCFAnalysesRegistry.defaultAnalysis)
val schedule = scenario.computeSchedule(propertyStore, FPCFAnalysesRegistry.defaultAnalysis, cleanupSpec)
schedules ::= schedule

if (trace) { debug("analysis progress", "executing " + schedule) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ package fpcf

case class PhaseConfiguration[A](
propertyKinds: PropertyKindsConfiguration,
scheduled: List[ComputationSpecification[A]]
scheduled: List[ComputationSpecification[A]],
toDelete: Set[Int] = Set.empty
)
12 changes: 12 additions & 0 deletions OPAL/si/src/main/scala/org/opalj/fpcf/PropertyKey.scala
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ object PropertyKey {
*/
def name(id: Int): String = propertyKeyNames(id)

def idByName(name: String): Int =
getByName(name).id

/**
* 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)

final def name(eOptionP: SomeEOptionP): String = name(eOptionP.pk.id)
Expand Down
52 changes: 47 additions & 5 deletions OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ abstract class PropertyStore {
//
//

private val externalInformation = new ConcurrentHashMap[AnyRef, AnyRef]()
private[this] val externalInformation = new ConcurrentHashMap[AnyRef, AnyRef]()
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.
Expand Down Expand Up @@ -500,6 +501,26 @@ abstract class PropertyStore {

protected 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)) {
clearPK(index)
}
index += 1
}
}

/**
* 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.
*
Expand Down Expand Up @@ -539,7 +560,17 @@ abstract class PropertyStore {
)
}

protected var subPhaseId: Int = 0
final def setupPhase(configuration: PropertyKindsConfiguration, toDelete: Set[Int]): Unit = {
setupPhase(
configuration.propertyKindsComputedInThisPhase,
configuration.propertyKindsComputedInLaterPhase,
configuration.suppressInterimUpdates,
configuration.collaborativelyComputedPropertyKindsFinalizationOrder,
toDelete
)
}

protected[this] var subPhaseId: Int = 0

protected var hasSuppressedNotifications: Boolean = false

Expand Down Expand Up @@ -571,7 +602,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");
Expand Down Expand Up @@ -639,14 +671,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 set up.
newPhaseInitialized(
propertyKindsComputedInThisPhase,
propertyKindsComputedInLaterPhase,
suppressInterimUpdates,
finalizationOrder
finalizationOrder,
currentPhaseDeletionMask
)

}

/**
Expand All @@ -657,7 +698,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*/ }

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -68,7 +70,10 @@ trait PropertyStoreBasedCommandLineConfig extends OPALCommandLineConfig { self:
generalArgs(
PropertyStoreThreadsNumArg,
PropertyStoreDebugArg,
SchedulingStrategyArg
SchedulingStrategyArg,
DisableCleanupArg,
KeepPropertyKeysArg,
ClearPropertyKeysArg
)

def setupPropertyStore(project: Project): (PropertyStore, Seconds) = {
Expand All @@ -93,3 +98,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)
}
}
21 changes: 16 additions & 5 deletions OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,7 +19,8 @@ import org.opalj.util.elidedAssert
*/
case class Schedule[A](
batches: List[PhaseConfiguration[A]],
initializationData: Map[ComputationSpecification[A], Any]
initializationData: Map[ComputationSpecification[A], Any],
cleanupSpec: Option[CleanupSpec]
) extends (
(
PropertyStore,
Expand All @@ -44,16 +46,24 @@ case class Schedule[A](
): List[(ComputationSpecification[A], A)] = {
implicit val logContext: LogContext = ps.logContext

val phases = cleanupSpec.map(scheduling.Cleanup.withPerPhaseCleanup(batches, ps, _)).getOrElse(batches)

var allExecutedAnalyses: List[(ComputationSpecification[A], A)] = Nil

batches.iterator.zipWithIndex foreach { batchId =>
val (PhaseConfiguration(configuration, css), id) = batchId
phases.iterator.zipWithIndex foreach { 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")
if (phase.toDelete.nonEmpty) {
info("analysis progress", s"to be deleted after this phase: " + phase.toDelete.map(PropertyKey.name))
}
}
time {
ps.setupPhase(configuration)
ps.setupPhase(configuration, phase.toDelete)

afterPhaseSetup(configuration)
elidedAssert(ps.isIdle, "the property store is not idle after phase setup")

Expand Down Expand Up @@ -88,8 +98,9 @@ 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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,15 +528,16 @@ class PKECPropertyStore(
startThreads(new PartialPropertiesFinalizerThread(_))

subPhaseId += 1

ps(AnalysisKeyId).clear()
}

clearObsoletePropertyKinds()
idle = true
}

private val interimStates: Array[ArrayBuffer[EPKState]] =
Array.fill(THREAD_COUNT)(null)
override protected def clearPK(id: Int): Unit = ps(id).clear()

private[this] val interimStates: Array[ArrayBuffer[EPKState]] =
private val successors: Array[EPKState => Iterable[EPKState]] =
Array.fill(THREAD_COUNT)(null)

Expand Down
Loading