Skip to content
Open
Show file tree
Hide file tree
Changes from 11 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
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
)
10 changes: 10 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 @@ -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
// ===============================================
Expand All @@ -123,6 +127,12 @@ object PropertyKey {
*/
def name(id: Int): String = propertyKeyNames(id)

def idByName(name: String): Int =
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)

final def name(eOptionP: SomeEOptionP): String = name(eOptionP.pk.id)
Expand Down
25 changes: 23 additions & 2 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,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.
*
Expand Down Expand Up @@ -534,7 +548,8 @@ abstract class PropertyStore {
configuration.propertyKindsComputedInThisPhase,
configuration.propertyKindsComputedInLaterPhase,
configuration.suppressInterimUpdates,
configuration.collaborativelyComputedPropertyKindsFinalizationOrder
configuration.collaborativelyComputedPropertyKindsFinalizationOrder,
toDelete = Set.empty
)
}

Expand Down Expand Up @@ -570,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");
Expand All @@ -584,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.
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)
}
}
23 changes: 19 additions & 4 deletions OPAL/si/src/main/scala/org/opalj/fpcf/Schedule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,29 @@ 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 =>
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 +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
}
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 clearSlot(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
71 changes: 71 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,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
}

}
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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -825,11 +825,14 @@ final class PKESequentialPropertyStore protected (
}
} while (continueComputation)

clearObsoletePropertyKinds()
idle = true

if (exception != null) throw exception;
}

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

def shutdown(): Unit = {}
}

Expand Down