Skip to content

Commit e6eff95

Browse files
authored
Ifds unit management refactoring #130 (#161)
1 parent 2203941 commit e6eff95

25 files changed

Lines changed: 981 additions & 625 deletions

jacodb-analysis/README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ The analysis entry point is the [runAnalysis] method. To call it, you have to pr
1717
* `graph` — an application graph that is used for analysis. To obtain this graph, one should call the [newApplicationGraphForAnalysis] method.
1818
* `unitResolver` — an object that groups methods into units. Choose one from `UnitResolversLibrary`.
1919
Note that, in general, larger units mean more precise but also more resource-consuming analysis.
20-
* `ifdsUnitRunner` — an [IfdsUnitRunner] instance, which is used to analyze each unit. This is what defines concrete analysis.
21-
Ready-to-use runners are located in `RunnersLibrary`.
20+
* `ifdsUnitRunnerFactory` — an [IfdsUnitRunnerFactory] instance, that can create runners. Runners are used to analyze each unit.
21+
So this factory is what define concrete analysis.
22+
Ready-to-use runner factories are located in `RunnersLibrary`.
2223
* `methods` — a list of methods to analyze.
2324

2425
For example, to detect the unused variables in the given `analyzedClass` methods, you may run the following code
@@ -31,9 +32,9 @@ val applicationGraph = runBlocking {
3132

3233
val methodsToAnalyze = analyzedClass.declaredMethods
3334
val unitResolver = MethodUnitResolver
34-
val runner = UnusedVariableRunner
35+
val runnerFactory = UnusedVariableRunnerFactory
3536

36-
runAnalysis(applicationGraph, unitResolver, runner, methodsToAnalyze)
37+
runAnalysis(applicationGraph, unitResolver, runnerFactory, methodsToAnalyze)
3738
```
3839

3940
## Implemented runners
@@ -47,29 +48,28 @@ By now, the following runners are implemented:
4748

4849
## Implementing your own analysis
4950

50-
To implement a simple one-pass analysis, use [IfdsBaseUnitRunner].
51+
To implement a simple one-pass analysis, use [IfdsBaseUnitRunnerFactory].
5152
To instantiate it, you need an [AnalyzerFactory] instance, which is an object that can create [Analyzer] via
5253
[JcApplicationGraph].
5354

5455
To instantiate an [Analyzer] interface, you have to specify the following:
5556

5657
* `flowFunctions`, which describe the dataflow facts and their transmissions during the analysis;
5758

58-
* how these facts produce vulnerabilities, i.e., you have to implement `getSummaryFacts` and
59-
`getSummaryFactsPostIfds` methods.
59+
* semantics for these dataflow facts, i.e. how these facts produce vulnerabilities, summaries, etc.
60+
This should be done by implementing `handleNewEdge`, `handleNewCrossUnitCall` and `handleIfdsResult` methods.
6061

61-
To implement bidirectional analysis, you may use composite [SequentialBidiIfdsUnitRunner] and [ParallelBidiIfdsUnitRunner].
62+
To implement bidirectional analysis, you may use composite [BidiIfdsUnitRunnerFactory].
6263

6364
<!--- MODULE jacodb-analysis -->
6465
<!--- INDEX org.jacodb.analysis -->
6566

6667
[runAnalysis]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis/run-analysis.html
6768
[newApplicationGraphForAnalysis]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.graph/new-application-graph-for-analysis.html
68-
[IfdsUnitRunner]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-ifds-unit-runner/index.html
69+
[IfdsUnitRunnerFactory]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-ifds-unit-runner-factory/index.html
6970
[JcClasspath]: https://jacodb.org/docs/jacodb-api/org.jacodb.api/-jc-classpath/index.html
70-
[IfdsBaseUnitRunner]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-ifds-base-unit-runner/index.html
71+
[IfdsBaseUnitRunnerFactory]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-ifds-base-unit-runner-factory/index.html
7172
[AnalyzerFactory]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-analyzer-factory/index.html
7273
[Analyzer]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-analyzer/index.html
7374
[JcApplicationGraph]: https://jacodb.org/docs/jacodb-api/org.jacodb.api.analysis/-jc-application-graph/index.html
74-
[SequentialBidiIfdsUnitRunner]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-sequential-bidi-ifds-unit-runner/index.html
75-
[ParallelBidiIfdsUnitRunner]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-parallel-bidi-ifds-unit-runner/index.html
75+
[BidiIfdsUnitRunnerFactory]: https://jacodb.org/docs/jacodb-analysis/org.jacodb.analysis.engine/-bidi-ifds-unit-runner-factory/index.html

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/AnalysisMain.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ package org.jacodb.analysis
1919

2020
import kotlinx.serialization.Serializable
2121
import mu.KLogging
22-
import org.jacodb.analysis.engine.IfdsUnitManager
23-
import org.jacodb.analysis.engine.IfdsUnitRunner
24-
import org.jacodb.analysis.engine.Summary
22+
import org.jacodb.analysis.engine.IfdsUnitRunnerFactory
23+
import org.jacodb.analysis.engine.MainIfdsUnitManager
24+
import org.jacodb.analysis.engine.SummaryStorage
2525
import org.jacodb.analysis.engine.UnitResolver
2626
import org.jacodb.analysis.engine.VulnerabilityInstance
2727
import org.jacodb.analysis.graph.newApplicationGraphForAnalysis
@@ -45,14 +45,14 @@ data class AnalysisConfig(val analyses: Map<String, AnalysesOptions>)
4545
* Usually built by [newApplicationGraphForAnalysis].
4646
*
4747
* @param unitResolver instance of [UnitResolver] which splits all methods into groups of methods, called units.
48-
* Units are analyzed concurrently, one unit will be analyzed with one call to [IfdsUnitRunner.run] method.
48+
* Units are analyzed concurrently, one unit will be analyzed with one call to [IfdsUnitRunnerFactory.newRunner] method.
4949
* In general, larger units mean more precise, but also more resource-consuming analysis, so [unitResolver] allows
5050
* to reach compromise.
51-
* It is guaranteed that [Summary] passed to all units is the same, so they can share information through it.
51+
* It is guaranteed that [SummaryStorage] passed to all units is the same, so they can share information through it.
5252
* However, the order of launching and terminating analysis for units is an implementation detail and may vary even for
5353
* consecutive calls of this method with same arguments.
5454
*
55-
* @param ifdsUnitRunner an [IfdsUnitRunner] instance that will be launched for each unit.
55+
* @param ifdsUnitRunnerFactory an [IfdsUnitRunnerFactory] instance that will be launched for each unit.
5656
* This is the main argument that defines the analysis.
5757
*
5858
* @param methods the list of method for analysis.
@@ -66,9 +66,9 @@ data class AnalysisConfig(val analyses: Map<String, AnalysesOptions>)
6666
fun runAnalysis(
6767
graph: JcApplicationGraph,
6868
unitResolver: UnitResolver<*>,
69-
ifdsUnitRunner: IfdsUnitRunner,
69+
ifdsUnitRunnerFactory: IfdsUnitRunnerFactory,
7070
methods: List<JcMethod>,
7171
timeoutMillis: Long = Long.MAX_VALUE
7272
): List<VulnerabilityInstance> {
73-
return IfdsUnitManager(graph, unitResolver, ifdsUnitRunner, methods, timeoutMillis).analyze()
73+
return MainIfdsUnitManager(graph, unitResolver, ifdsUnitRunnerFactory, methods, timeoutMillis).analyze()
7474
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2022 UnitTestBot contributors (utbot.org)
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.jacodb.analysis.engine
18+
19+
import org.jacodb.api.analysis.JcApplicationGraph
20+
import java.util.concurrent.ConcurrentHashMap
21+
22+
/**
23+
* Handlers of [AbstractAnalyzer] produce some common events like new [SummaryEdgeFact]s, new [CrossUnitCallFact]s, etc.
24+
* Inheritors may override all these handlers, and also they can extend them by calling the super-method and adding
25+
* their own event
26+
*
27+
* @property verticesWithTraceGraphNeeded For all vertices added to this set,
28+
* a [TraceGraphFact] will be produced at [handleIfdsResult]
29+
*
30+
* @property isMainAnalyzer Iff this property is set to true, handlers will
31+
* 1. Produce [NewSummaryFact] events with [SummaryEdgeFact]s and [CrossUnitCallFact]s
32+
* 2. Will produce [EdgeForOtherRunnerQuery] for each cross-unit call
33+
*
34+
* Usually this should be set to true for forward analyzers (which are expected to tell anything they found),
35+
* but in backward analyzers this should be set to false
36+
*/
37+
abstract class AbstractAnalyzer(private val graph: JcApplicationGraph) : Analyzer {
38+
protected val verticesWithTraceGraphNeeded: MutableSet<IfdsVertex> = ConcurrentHashMap.newKeySet()
39+
40+
abstract val isMainAnalyzer: Boolean
41+
42+
/**
43+
* If the edge is start-to-end and [isMainAnalyzer] is true,
44+
* produces a [NewSummaryFact] with this summary edge.
45+
* Otherwise, returns empty list.
46+
*/
47+
override fun handleNewEdge(edge: IfdsEdge): List<AnalysisDependentEvent> {
48+
return if (isMainAnalyzer && edge.v.statement in graph.exitPoints(edge.method)) {
49+
listOf(NewSummaryFact(SummaryEdgeFact(edge)))
50+
} else {
51+
emptyList()
52+
}
53+
}
54+
55+
/**
56+
* If [isMainAnalyzer] is set to true, produces a [NewSummaryFact] with given [fact]
57+
* and also produces [EdgeForOtherRunnerQuery]
58+
*/
59+
override fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List<AnalysisDependentEvent> {
60+
return if (isMainAnalyzer) {
61+
verticesWithTraceGraphNeeded.add(fact.callerVertex)
62+
listOf(NewSummaryFact(fact), EdgeForOtherRunnerQuery(IfdsEdge(fact.calleeVertex, fact.calleeVertex)))
63+
} else {
64+
emptyList()
65+
}
66+
}
67+
68+
/**
69+
* Produces trace graphs for all vertices added to [verticesWithTraceGraphNeeded]
70+
*/
71+
override fun handleIfdsResult(ifdsResult: IfdsResult): List<AnalysisDependentEvent> {
72+
val traceGraphs = verticesWithTraceGraphNeeded.map {
73+
ifdsResult.resolveTraceGraph(it)
74+
}
75+
76+
return traceGraphs.map {
77+
NewSummaryFact(TraceGraphFact(it))
78+
}
79+
}
80+
}

jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/FlowFunctions.kt renamed to jacodb-analysis/src/main/kotlin/org/jacodb/analysis/engine/AnalyzerFactory.kt

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -56,51 +56,48 @@ interface FlowFunctionsSpace {
5656
}
5757

5858
/**
59-
* [Analyzer] interface describes how facts are propagated and how vulnerabilities are produced by these facts during
60-
* the run of tabulation algorithm by [IfdsBaseUnitRunner].
61-
*
62-
* There are two methods that can turn facts into vulnerabilities or other [SummaryFact]s: [getSummaryFacts] and
63-
* [getSummaryFactsPostIfds]. First is called during the analysis, each time a new path edge is found, and second
64-
* is called only after all path edges were found.
65-
* While some analyses really need full set of facts to find vulnerabilities, most analyses can report [SummaryFact]s
66-
* right after some fact is reached, so [getSummaryFacts] is a recommended way to report vulnerabilities when possible.
59+
* [Analyzer] interface describes how facts are propagated and how [AnalysisDependentEvent]s are produced by these facts during
60+
* the run of tabulation algorithm by [BaseIfdsUnitRunner].
6761
*
6862
* Note that methods and properties of this interface may be accessed concurrently from different threads,
6963
* so the implementations should be thread-safe.
7064
*
7165
* @property flowFunctions a [FlowFunctionsSpace] instance that describes how facts are generated and propagated
7266
* during run of tabulation algorithm.
73-
*
74-
* @property saveSummaryEdgesAndCrossUnitCalls when true, summary edges and cross-unit calls will be automatically
75-
* saved to summary (usually this property is true for forward analyzers and false for backward analyzers).
7667
*/
7768
interface Analyzer {
7869
val flowFunctions: FlowFunctionsSpace
7970

80-
val saveSummaryEdgesAndCrossUnitCalls: Boolean
81-
get() = true
71+
/**
72+
* This method is called by [BaseIfdsUnitRunner] each time a new path edge is found.
73+
*
74+
* @return [AnalysisDependentEvent]s that are produced by this edge.
75+
* Usually these are [NewSummaryFact] events with [SummaryEdgeFact] or [VulnerabilityLocation] facts
76+
*/
77+
fun handleNewEdge(edge: IfdsEdge): List<AnalysisDependentEvent>
8278

8379
/**
84-
* This method is called by [IfdsBaseUnitRunner] each time a new path edge is found.
80+
* This method is called by [BaseIfdsUnitRunner] each time a new cross-unit called is observed.
8581
*
86-
* @return [SummaryFact]s that are produced by this edge, that need to be saved to summary.
82+
* @return [AnalysisDependentEvent]s that are produced by this [fact].
8783
*/
88-
fun getSummaryFacts(edge: IfdsEdge): List<SummaryFact> = emptyList()
84+
fun handleNewCrossUnitCall(fact: CrossUnitCallFact): List<AnalysisDependentEvent>
8985

9086
/**
91-
* This method is called once by [IfdsBaseUnitRunner] when the propagation of facts is finished
87+
* This method is called once by [BaseIfdsUnitRunner] when the propagation of facts is finished
9288
* (normally or due to cancellation).
9389
*
94-
* @return [SummaryFact]s that can be obtained after the facts propagation was completed.
90+
* @return [AnalysisDependentEvent]s that should be processed after the facts propagation was completed
91+
* (usually these are some [NewSummaryFact]s).
9592
*/
96-
fun getSummaryFactsPostIfds(ifdsResult: IfdsResult): List<SummaryFact> = emptyList()
93+
fun handleIfdsResult(ifdsResult: IfdsResult): List<AnalysisDependentEvent>
9794
}
9895

9996
/**
10097
* A functional interface that allows to produce [Analyzer] by [JcApplicationGraph].
10198
*
102-
* It simplifies instantiation of [IfdsUnitRunner]s because this way you don't have to pass graph and reversed
103-
* graph to [Analyzer]s directly, relying on runner to do it by itself.
99+
* It simplifies instantiation of [IfdsUnitRunnerFactory]s because this way you don't have to pass graph and reversed
100+
* graph to analyzers' constructors directly, relying on runner to do it by itself.
104101
*/
105102
fun interface AnalyzerFactory {
106103
fun newAnalyzer(graph: JcApplicationGraph): Analyzer

0 commit comments

Comments
 (0)