Skip to content

Adapt tutorials to changes in OPAL #286

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/site/tutorial/ClassImmutabilityAnalysis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import org.opalj.br.analyses.ProjectInformationKeys
import org.opalj.br.analyses.SomeProject
import org.opalj.br.fpcf.BasicFPCFEagerAnalysisScheduler
import org.opalj.br.fpcf.BasicFPCFLazyAnalysisScheduler
import org.opalj.fpcf.FPCFAnalysesManagerKey
import org.opalj.br.fpcf.FPCFAnalysis
import org.opalj.br.fpcf.FPCFAnalysisScheduler
import org.opalj.fpcf.Entity
import org.opalj.fpcf.EOptionP
import org.opalj.fpcf.FallbackReason
import org.opalj.fpcf.FPCFAnalysesManagerKey
import org.opalj.fpcf.InterimResult
import org.opalj.fpcf.OrderedProperty
import org.opalj.fpcf.ProperPropertyComputationResult
Expand Down
31 changes: 17 additions & 14 deletions src/site/tutorial/CollaborativeAnalyses.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,12 @@ Now, let's implement the (simplified) analysis.
As usual, we start by creating an analysis class with an analysis function:
```scala
class InstantiatedTypesAnalysis(val project: SomeProject) extends FPCFAnalysis {
implicit private val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey)
implicit private val contextProvider: ContextProvider = project.get(ContextProviderKey)

def analyzeMethod(method: DeclaredMethod): PropertyComputationResult = { [...] }
}
```
We need a [`ProjectInformationKey`](/library/api/SNAPSHOT/org/opalj/br/analyses/ProjectInformationKey.html) here, namely the `DeclaredMethodsKey`, as this will later be needed implicitly to resolve calls with the call graph.
We need a [`ProjectInformationKey`](/library/api/SNAPSHOT/org/opalj/br/analyses/ProjectInformationKey.html) here, namely the `ContextProviderKey`, as this will later be needed implicitly to resolve calls with the call graph.
The analysis function takes a [`DeclaredMethod`](/library/api/SNAPSHOT/org/opalj/br/DeclaredMethod.html) (a representation of a method in the context of its class) as the entity to be analyzed as we want to find all classes instantiated by methods that potentially called (i.e., that are not *dead*).
Note that unlike most analyses, we will however *not* compute a result just for this entity, we just use it to compute its effect on the set of instantiated classes *anywhere* in the analyzed program.

Expand Down Expand Up @@ -112,7 +112,7 @@ We use a [`PartialResult`](/library/api/SNAPSHOT/org/opalj/fpcf/NoResult.html) h
The partial result takes the entity (we use `project` here, since the set of instantiated classes is global to the whole program that is analyzed) and the key of the property that we compute.
Finally, it takes a function that will get the current value of that property and computes an update to it.
To do so, we check whether there already is a property present and extract its upper bound.
If that upper bound already contains our class, we return `None` to signal that no update is necessary, otherwise we create an updated result, which is an [`InterimEUBP`](/library/api/SNAPSHOT/org/opalj/fpcf/InterimEUBP.html), i.e., a not yet final result consisting of an entity (`project`) and its property, which is the old set of instantiated classes extended by the class type of the analyzed constructor.
If that upper bound already contains our class, we return `None` to signal that no update is necessary, otherwise we create an updated result, which is an [`InterimEUBP`](/library/api/SNAPSHOT/org/opalj/fpcf/InterimEUBP.html), i.e., a not yet final result consisting of an entity (E; `project`) and and upper bound (UB) for its property (P), which is the old set of instantiated classes extended by the class type of the analyzed constructor.
If, on the other hand, no property has been computed so far, the update function will be called with an [`EPK`](/library/api/SNAPSHOT/org/opalj/fpcf/EPK.html), i.e., a tuple of the entity and the key of the property.
In that case, we return property that contains just the class type of the analyzed constructor.

Expand Down Expand Up @@ -167,12 +167,13 @@ def checkCallers(callersProperty: EOptionP[DeclaredMethod, Callers]): PropertyCo
```

Now we can iterate over all callers known so far (the second part of the tuple, the actual program counter of the call is irrelevant here).
The `callers` method implicitly requires the `DeclaredMethods`, which is why we got that at the beginning of our analysis class.
The `callers` method takes the callee method (the entity `e` of our callers property) to properly resolve calls.
It also implicitly requires the `ContextProvider`, which is why we got that at the beginning of our analysis class.
```scala
def checkCallers(callersProperty: EOptionP[DeclaredMethod, Callers]): PropertyComputationResult = {
[...]

for((caller, _, isDirect) <- callers.callers) {
for((caller, _, isDirect) <- callers.callers(callersProperty.e)) {
if (!isDirect)
return result()

Expand All @@ -192,7 +193,7 @@ If it isn't, again the constructor must have been called explicitly.

If the caller is a constructor, we now check whether it belongs to a direct subclass:
```scala
for((caller, _, isDirect) <- callers.callers){
for((caller, _, isDirect) <- callers.callers(callersProperty.e)){
[...]

val callerClass = project.classFile(caller.declaringClassType)
Expand All @@ -209,7 +210,7 @@ The same is true if we know the class, but it has no superclass (this mainly con
If we didn't return a result yet, we have established that the caller is a constructor of a direct subclass.
However, it may still have an explicit call to the analyzed constructor, thus we have to look for such call in its instructions:
```scala
for((caller, _, isDirect) <- callers.callers){
for((caller, _, isDirect) <- callers.callers(callersProperty.e)){
[...]

val body = caller.definedMethod.body.get
Expand All @@ -224,14 +225,15 @@ Finally, they also can't be abstract or implemented by a native method, thus the

Now we can look for explicit instantiations of the analyzed constructor's class:
```scala
for((caller, _, isDirect) <- callers.callers){
for((caller, _, isDirect) <- callers.callers(callersProperty.e)){
[...]

if(body.exists((_, instruction) => instruction == NEW(instantiatedType)))
if (body.exists(_.instruction == NEW(instantiatedType)))
return result()
}
```
If there is a `NEW` instruction for that class, that is an explicit instantiation and we return a respective result.
If there is a `NEW` instruction for that class, that is an explicit instantiation.
Thus, we return a respective result.
Otherwise, we now know that this caller is a constructor of a direct subclass that only calls the analyzed constructor as part of its own initialization and thus, it doesn't actually instantiate the class of the analyzed constructor.

If we didn't find any caller that caused an explicit instantiation, there still might be more callers that we just don't know yet.
Expand Down Expand Up @@ -280,15 +282,15 @@ This completes our analysis, now we have to provide a scheduler for it.
We use an [`FPCFTriggeredAnalysisScheduler`](/library/api/SNAPSHOT/org/opalj/br/fpcf/FPCFTriggeredAnalysisScheduler.html)<sup title="The BasicFPCFTriggeredAnalysisScheduler provides an empty implementation for some rarely used methods.">[note]</sup> that allows us to trigger our analysis whenever a method is found to be reachable in the call graph.
```scala
object InstantiatedTypesAnalysisScheduler extends BasicFPCFTriggeredAnalysisScheduler {
override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey)
override def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey)

override def uses: Set[PropertyBounds] = PropertyBounds.ubs(InstantiatedTypes, Callers)

[...]
}
```
As usual, we first have to provide the used `ProjectInformationKeys` and fixed-point properties.
Remember we used the `DeclaredMethodsKey` in our analysis, so we have to specify it here.
Remember we used the `ContextProviderKey` in our analysis, so we have to specify it here.
Also, we use upper bounds from the `InstantiatedTypes` and `Callers` lattices.
Note that `PropertyBounds.ubs(...)` is a shorthand for `Set(PropertyBounds.ub(...), ...)`.

Expand Down Expand Up @@ -333,7 +335,7 @@ As a last step, we implement a simple runner to test our analysis.
object InstantiatedTypesRunner extends ProjectAnalysisApplication {
override def doAnalyze(project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean): BasicReport = {
val (propertyStore, _) = project.get(FPCFAnalysesManagerKey).runAll(
CHACallGraphAnalysisScheduler,
CallGraphAnalysisScheduler,
InstantiatedTypesAnalysisScheduler
)

Expand All @@ -346,7 +348,8 @@ object InstantiatedTypesRunner extends ProjectAnalysisApplication {
}
}
```
It executes our analysis alongside a class-hierarchy analysis (CHA) call graph and prints the number of instantiated types to the console.
It executes our analysis alongside a call-graph analysis and prints the number of instantiated types to the console.
The kind of call-graph analysis executed actually depends on the `ContextProviderKey` (or rather its `TypeIterator` special case), which we didn't set up here, so it defaults to a class-hierarchy analysis (CHA).
Note that when querying the property store for the `InstantiatedTypes` property key, we know that it must be a final result because all analyses have been completed by that point.
Remember to specify the program that you want to analyze with the "-cp=<some path>" parameter.

Expand Down
6 changes: 3 additions & 3 deletions src/site/tutorial/FixedPointAnalyses.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ Because OPAL doesn't know in advance that we will only ever query the property s
Thus, we provide a second entry point to our analysis, `lazilyAnalyzeClassImmutability`, that takes any type of entity and, if it is a classfile, analyzes it.
We just throw an exception if the entity is not a classfile, as we can only process classfiles.

Now we are ready to implement a [`FPCFLazyAnalysisScheduler`](/library/api/SNAPSHOT/org/opalj/br/fpcf/FPCFLazyAnalysisScheduler.html):
Now we are ready to implement an [`FPCFLazyAnalysisScheduler`](/library/api/SNAPSHOT/org/opalj/br/fpcf/FPCFLazyAnalysisScheduler.html):
```scala
object LazyClassImmutabilityAnalysis extends ClassImmutabilityAnalysisScheduler with BasicFPCFLazyAnalysisScheduler {

Expand All @@ -338,7 +338,7 @@ As before, we finally have to return the analysis.
## Running the Analysis

Finally it is time to try our analysis.
To do so easily, we extend [ProjectAnalysisApplication]() which provides us with an implicit `main` method that parses parameters for us, most importantly the "-cp=<some path>" parameter that lets users specify the path to a project that they want to analyze.
To do so easily, we extend [ProjectAnalysisApplication](/library/api/SNAPSHOT/org/opalj/br/analyses/ProjectAnalysisApplication.html) which provides us with an implicit `main` method that parses parameters for us, most importantly the "-cp=<some path>" parameter that lets users specify the path to a project that they want to analyze.
```scala
object ClassImmutabilityRunner extends ProjectAnalysisApplication {
override def doAnalyze(project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean): BasicReport = { [...] }
Expand All @@ -356,7 +356,7 @@ override def doAnalyze(project: Project[URL], parameters: Seq[String], isInterru
[...]
}
```
We use the [`FPCFAnalysesManagerKey`](/library/api/SNAPSHOT/org/opalj/br/fpcf/FPCFAnalysesManagerKey$.html) to get an [`FPCFAnalysesManager`](/library/api/SNAPSHOT/org/opalj/br/fpcf/FPCFAnalysesManager.html) that will run our analyses.
We use the [`FPCFAnalysesManagerKey`](/library/api/SNAPSHOT/org/opalj/fpcf/FPCFAnalysesManagerKey$.html) to get an [`FPCFAnalysesManager`](/library/api/SNAPSHOT/org/opalj/fpcf/FPCFAnalysesManager.html) that will run our analyses.
We just pass all analyses that we want to execute to the `runAll` method.
Note that we assume that a `LazyFieldImmutabilityAnalysis` has been implemented as well.
As long as that doesn't exist, you can remove that line and OPAL will use the fallback value of the `FieldImmutability` lattice whenever a field immutability is queried.
Expand Down
18 changes: 9 additions & 9 deletions src/site/tutorial/InstantiatedTypesAnalysis.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import org.opalj.collection.immutable.UIDSet
import org.opalj.br.DeclaredMethod
import org.opalj.br.ClassType
import org.opalj.br.analyses.BasicReport
import org.opalj.br.analyses.DeclaredMethods
import org.opalj.br.analyses.DeclaredMethodsKey
import org.opalj.br.analyses.Project
import org.opalj.br.analyses.ProjectAnalysisApplication
import org.opalj.br.analyses.ProjectInformationKeys
Expand All @@ -14,13 +12,15 @@ import org.opalj.br.fpcf.FPCFAnalysis
import org.opalj.br.fpcf.properties.cg.Callers
import org.opalj.br.fpcf.properties.cg.NoCallers
import org.opalj.br.fpcf.BasicFPCFTriggeredAnalysisScheduler
import org.opalj.fpcf.FPCFAnalysesManagerKey
import org.opalj.br.fpcf.ContextProviderKey
import org.opalj.br.fpcf.analyses.ContextProvider
import org.opalj.br.instructions.NEW
import org.opalj.fpcf.Entity
import org.opalj.fpcf.EOptionP
import org.opalj.fpcf.EPK
import org.opalj.fpcf.FallbackReason
import org.opalj.fpcf.FinalP
import org.opalj.fpcf.FPCFAnalysesManagerKey
import org.opalj.fpcf.InterimEUBP
import org.opalj.fpcf.InterimPartialResult
import org.opalj.fpcf.InterimUBP
Expand All @@ -36,7 +36,7 @@ import org.opalj.fpcf.PropertyMetaInformation
import org.opalj.fpcf.PropertyStore
import org.opalj.fpcf.SomeEPS
import org.opalj.fpcf.UBP
import org.opalj.tac.fpcf.analyses.cg.CHACallGraphAnalysisScheduler
import org.opalj.tac.fpcf.analyses.cg.CallGraphAnalysisScheduler

/* LATTICE */

Expand Down Expand Up @@ -69,7 +69,7 @@ object InstantiatedTypes extends InstantiatedTypesPropertyMetaInformation {
/* ANALYSIS */

class InstantiatedTypesAnalysis(val project: SomeProject) extends FPCFAnalysis {
implicit private val declaredMethods: DeclaredMethods = project.get(DeclaredMethodsKey)
implicit private val contextProvider: ContextProvider = project.get(ContextProviderKey)

def analyzeMethod(method: DeclaredMethod): PropertyComputationResult = {
if (method.name != "<init>")
Expand Down Expand Up @@ -111,7 +111,7 @@ class InstantiatedTypesAnalysis(val project: SomeProject) extends FPCFAnalysis {
if (callers.hasCallersWithUnknownContext || callers.hasVMLevelCallers)
return result()

for ((caller, _, isDirect) <- callers.callers) {
for ((caller, _, isDirect) <- callers.callers(callersProperty.e)) {
if (!isDirect)
return result()

Expand All @@ -125,7 +125,7 @@ class InstantiatedTypesAnalysis(val project: SomeProject) extends FPCFAnalysis {

val body = caller.definedMethod.body.get

if (body.exists((_, instruction) => instruction == NEW(instantiatedType)))
if (body.exists(_.instruction == NEW(instantiatedType)))
return result()
}

Expand All @@ -151,7 +151,7 @@ class InstantiatedTypesAnalysis(val project: SomeProject) extends FPCFAnalysis {
/* SCHEDULER */

object InstantiatedTypesAnalysisScheduler extends BasicFPCFTriggeredAnalysisScheduler {
override def requiredProjectInformation: ProjectInformationKeys = Seq(DeclaredMethodsKey)
override def requiredProjectInformation: ProjectInformationKeys = Seq(ContextProviderKey)

override def uses: Set[PropertyBounds] = PropertyBounds.ubs(InstantiatedTypes, Callers)

Expand All @@ -173,7 +173,7 @@ object InstantiatedTypesAnalysisScheduler extends BasicFPCFTriggeredAnalysisSche
object InstantiatedTypesRunner extends ProjectAnalysisApplication {
override def doAnalyze(project: Project[URL], parameters: Seq[String], isInterrupted: () => Boolean): BasicReport = {
val (propertyStore, _) = project.get(FPCFAnalysesManagerKey).runAll(
CHACallGraphAnalysisScheduler,
CallGraphAnalysisScheduler,
InstantiatedTypesAnalysisScheduler
)

Expand Down
4 changes: 2 additions & 2 deletions src/site/tutorial/Lattices.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ sealed trait ClassImmutabilityPropertyMetaInformation extends PropertyMetaInform
```
We use it to provide the type of the property.

Second, the property companion object provides the [PropertyKey]():
Second, the property companion object provides the [PropertyKey](/library/api/SNAPSHOT/org/opalj/fpcf/PropertyKey.html):
```scala
object ClassImmutability extends ClassImmutabilityPropertyMetaInformation {
final val key: PropertyKey[ClassImmutability] = PropertyKey.create(
Expand Down Expand Up @@ -99,7 +99,7 @@ override def checkIsEqualOrBetterThan(e: Entity, other: ClassImmutability): Unit
```
However, it may be better to provide optimized implementations for your individual lattice values if the `meet` operation is not trivial.

The second option to consider is [`AggregatedProperty`](/library/api/SNAPSHOT/org/opalj/br/fpcf/properties/AggregatedProperty.html).
The second option to consider is [`AggregatedProperty`](/library/api/SNAPSHOT/org/opalj/fpcf/AggregatedProperty.html).
Some properties really represent an aggregation of another property, e.g., `ClassImmutability` aggregates the `FieldImmutability` of a class' instance fields.
In such cases, one often needs to convert between corresponding values of the two lattices.
Also, the partial order and thus `meet` operator are equivalent and need to be defined only once.
Expand Down
Loading