Skip to content
Open
Show file tree
Hide file tree
Changes from 16 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
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,15 @@ 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
Expand Down Expand Up @@ -132,7 +135,10 @@ object Immutability extends ProjectsAnalysisApplication {
analysisLevelArg !,
ignoreLazyInitializationArg !,
ConfigurationNameArg !,
EscapeArg
EscapeArg,
DisableCleanupArg,
KeepPropertyKeysArg,
ClearPropertyKeysArg
)
init()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,13 @@ 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
Expand Down Expand Up @@ -94,7 +97,10 @@ object Purity extends ProjectsAnalysisApplication {
RaterArg !,
IndividualArg,
OutputDirArg,
PackagesArg
PackagesArg,
DisableCleanupArg,
KeepPropertyKeysArg,
ClearPropertyKeysArg
)
generalArgs(
DomainArg
Expand Down
3 changes: 2 additions & 1 deletion OPAL/si/src/main/scala/org/opalj/fpcf/AnalysisScenario.scala
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,8 @@ class AnalysisScenario[A](val ps: PropertyStore) {

Schedule(
scheduledBatches,
initializationData
initializationData,
None
)
}
}
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
34 changes: 31 additions & 3 deletions OPAL/si/src/main/scala/org/opalj/fpcf/PropertyStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ abstract class PropertyStore {
//

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 @@ -499,6 +500,26 @@ 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)) {
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 @@ -529,12 +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
configuration.collaborativelyComputedPropertyKindsFinalizationOrder,
toDelete
)
}

Expand Down Expand Up @@ -570,7 +592,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 All @@ -584,6 +607,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.
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 @@ -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)
}
}
27 changes: 22 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 @@ -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,
Expand All @@ -43,16 +45,26 @@ 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")
}
time {
ps.setupPhase(configuration)
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")

Expand Down Expand Up @@ -87,8 +99,13 @@ 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)
ps.setupPhase(Set.empty, Set.empty, toDelete = finalToDelete)

allExecutedAnalyses
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -519,13 +519,15 @@ class PKECPropertyStore(
startThreads(new PartialPropertiesFinalizerThread(_))

subPhaseId += 1

ps(AnalysisKeyId).clear()
}

clearObsoletePropertyKinds()
idle = true
}

override protected def clearPK(id: Int): Unit = ps(id).clear()

private[this] val interimStates: Array[ArrayBuffer[EPKState]] =
Array.fill(THREAD_COUNT)(null)
private[this] val successors: Array[EPKState => Iterable[EPKState]] =
Expand Down
66 changes: 66 additions & 0 deletions OPAL/si/src/main/scala/org/opalj/fpcf/scheduling/Cleanup.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj
package fpcf
package scheduling

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

/**
* Factory for creating [[CleanupSpec]]-objects, also handles calculation of per-phase cleanup
*/
object Cleanup {

/**
* 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 = {
val toKeep = keep.map(PropertyKey.idByName)
val toClear = clear.map(PropertyKey.idByName)
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 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 usedInAnyPhase: Set[Int] = Set.empty
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
index -= 1
}

schedule.indices.iterator.map { index =>
val producedHere = schedule(index)
val toDelete = ((producedInAnyPhase -- neededLater(index)) -- spec.keep) union spec.clear
producedHere.copy(toDelete = toDelete)
}.toList
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ abstract class MultiplePhaseScheduling extends SchedulingStrategy {
remainingAnalyses --= phaseAnalyses
computePhase(ps, phaseAnalyses, remainingAnalyses)
}

schedule
}

Expand Down Expand Up @@ -169,3 +168,9 @@ object MaximumPhaseScheduling extends MultiplePhaseScheduling {
(initialPhaseDependencyGraph.toMap, initialPhaseIndexToAnalyses.toMap)
}
}

object CleanupCalculation {
final val PropertiesToKeepKey = s"${ConfigKeyPrefix}KeepPropertyKeys"
final val PropertiesToRemoveKey = s"${ConfigKeyPrefix}ClearPropertyKeys"
final val DisableCleanupKey = s"${ConfigKeyPrefix}DisableCleanup"
}
Loading